From 8d07c38d32d4108c2a6927fdb307f8446740a645 Mon Sep 17 00:00:00 2001 From: Lensors Date: Sun, 11 May 2025 10:06:25 +0200 Subject: [PATCH] =?UTF-8?q?Ajout=20d=C3=A9placement=20blocs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projet/src/main/java/projet/Bloc.java | 27 +++++- .../main/java/projet/SauvegarderOrdre.java | 86 +++++++++++++++++++ .../src/main/java/projet/SupprimerBloc.java | 37 +++++--- Projet/src/main/webapp/WEB-INF/Accueil.jsp | 31 +++++++ .../src/main/webapp/WEB-INF/AfficherPage.jsp | 83 +++++++++++++++++- Projet/src/main/webapp/js/bloc.js | 9 +- Projet/src/main/webapp/js/drag-and-drop.js | 62 +++++++++++++ Projet/src/main/webapp/js/functionsBloc.js | 21 +++-- Projet/src/main/webapp/js/main.js | 2 + Projet/src/main/webapp/js/socket-bloc.js | 27 +++++- Projet/src/main/webapp/js/utils.js | 1 + Projet/src/main/webapp/styles.css | 4 + 12 files changed, 366 insertions(+), 24 deletions(-) create mode 100644 Projet/src/main/java/projet/SauvegarderOrdre.java diff --git a/Projet/src/main/java/projet/Bloc.java b/Projet/src/main/java/projet/Bloc.java index a633da1..3ef639e 100644 --- a/Projet/src/main/java/projet/Bloc.java +++ b/Projet/src/main/java/projet/Bloc.java @@ -197,7 +197,8 @@ public class Bloc extends ParamBD { } } - public static void supprimerBloc(int idBloc, int idU) { + public static boolean supprimerBloc(int idBloc, int idU) { + boolean reponse = false; try { Connection connexion = DriverManager.getConnection(bdURL, bdLogin, bdPassword); String sql = " DELETE FROM bloc" @@ -209,8 +210,32 @@ public class Bloc extends ParamBD { pst.setInt(2, idU); pst.executeUpdate(); + pst.close(); + connexion.close(); + reponse = true; } catch (SQLException e) { e.printStackTrace(); } + + return reponse; + } + + public static void miseAJourOrdreBloc(int idBloc, int ordre) { + try { + Connection connexion = DriverManager.getConnection(bdURL, bdLogin, bdPassword); + String sql = " UPDATE bloc" + + " SET ordre = ?" + + " WHERE id = ?" + + ";"; + PreparedStatement pst = connexion.prepareStatement(sql); + pst.setInt(1, ordre); + pst.setInt(2, idBloc); + pst.executeUpdate(); + + pst.close(); + connexion.close(); + } catch (SQLException e) { + e.printStackTrace(); + } } } diff --git a/Projet/src/main/java/projet/SauvegarderOrdre.java b/Projet/src/main/java/projet/SauvegarderOrdre.java new file mode 100644 index 0000000..aa0a567 --- /dev/null +++ b/Projet/src/main/java/projet/SauvegarderOrdre.java @@ -0,0 +1,86 @@ +package projet; + +import jakarta.servlet.ServletException; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; + +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonObject; +import javax.json.JsonReader; + + +@WebServlet("/SauvegarderOrdre") +public class SauvegarderOrdre extends HttpServlet { + private static final long serialVersionUID = 1L; + + + public SauvegarderOrdre() { + super(); + } + + + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + HttpSession session = request.getSession(); + Utilisateur u = (Utilisateur) session.getAttribute("utilisateur"); + + if(u != null) { + response.sendRedirect("AfficherPage"); + }else { + response.sendRedirect("/Projet/"); + } + } + + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + response.setContentType("application/json"); + response.setCharacterEncoding("UTF-8"); + + HttpSession session = request.getSession(); + Utilisateur u = (Utilisateur) session.getAttribute("utilisateur"); + + if (u == null) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().write("{\"error\": \"Utilisateur non connecté\"}"); + return; + } + + try (BufferedReader reader = request.getReader()) { + StringBuilder json = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + json.append(line); + } + + JsonReader jsonReader = Json.createReader(new StringReader(json.toString())); + JsonArray jsonArray = jsonReader.readArray(); + + for (int i = 0; i < jsonArray.size(); i++) { + JsonObject jsonObject = jsonArray.getJsonObject(i); + + if (!jsonObject.containsKey("id") || !jsonObject.containsKey("ordre")) { + continue; + } + + int id = Integer.parseInt(jsonObject.getString("id")); + int ordre = jsonObject.getInt("ordre"); + + Bloc.miseAJourOrdreBloc(id, ordre); + } + + response.setStatus(HttpServletResponse.SC_OK); + response.getWriter().write("{\"success\": true}"); + } catch (Exception e) { + e.printStackTrace(); + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + response.getWriter().write("{\"error\": \"Données invalides ou erreur serveur\"}"); + } + } + +} diff --git a/Projet/src/main/java/projet/SupprimerBloc.java b/Projet/src/main/java/projet/SupprimerBloc.java index 7cd53e1..d37b486 100644 --- a/Projet/src/main/java/projet/SupprimerBloc.java +++ b/Projet/src/main/java/projet/SupprimerBloc.java @@ -29,22 +29,35 @@ public class SupprimerBloc extends HttpServlet { } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + response.setContentType("application/json"); + response.setCharacterEncoding("UTF-8"); + HttpSession session = request.getSession(); Utilisateur u = (Utilisateur) session.getAttribute("utilisateur"); + + if (u == null) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().write("{\"error\": \"Non autorisé\"}"); + return; + } - if(u != null) { - String idBlocStr = request.getParameter("blocId"); - - if(idBlocStr == null || idBlocStr.isEmpty()) { - response.sendRedirect("AfficherPage"); - } else { - int idBloc = Integer.parseInt(idBlocStr); - Bloc.supprimerBloc(idBloc, u.getId()); - response.sendRedirect("AfficherPage"); - } - + String idBlocStr = request.getParameter("blocId"); + + if (idBlocStr == null || idBlocStr.isEmpty()) { + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + response.getWriter().write("{\"error\": \"ID manquant\"}"); + return; + } + + int idBloc = Integer.parseInt(idBlocStr); + boolean success = Bloc.supprimerBloc(idBloc, u.getId()); + + if (success) { + response.setStatus(HttpServletResponse.SC_OK); + response.getWriter().write("{\"success\": true}"); } else { - response.sendRedirect("/Projet/"); + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + response.getWriter().write("{\"error\": \"Suppression interdite\"}"); } } } diff --git a/Projet/src/main/webapp/WEB-INF/Accueil.jsp b/Projet/src/main/webapp/WEB-INF/Accueil.jsp index a1c1ec2..3ef93e7 100644 --- a/Projet/src/main/webapp/WEB-INF/Accueil.jsp +++ b/Projet/src/main/webapp/WEB-INF/Accueil.jsp @@ -6,6 +6,37 @@ +
+
+
+
+
+

+ + Bienvenue sur notre plateforme de prise de notes collaborative +

+

+ Ce site vous permet de créer facilement des pages de contenu interactives en utilisant du Markdown, d’organiser vos idées en blocs, et de collaborer en temps réel avec d’autres utilisateurs. +

+
    +
  • Éditeur intelligent : avec support des titres, listes, citations, et mise en forme automatique.
  • +
  • Partage de pages : invitez vos collaborateurs avec un accès en lecture ou en édition.
  • +
  • Sauvegarde en temps réel : vos modifications sont automatiquement enregistrées et partagées.
  • +
+

+ Une fois votre travail terminé, vous pouvez exporter vos pages dans plusieurs formats : +

+
    +
  • HTML — pour une intégration facile sur le web
  • +
  • LaTeX — idéal pour des documents scientifiques ou académiques
  • +
  • Markdown — pour garder un format brut et portable
  • +
+
+
+
+
+
+ diff --git a/Projet/src/main/webapp/WEB-INF/AfficherPage.jsp b/Projet/src/main/webapp/WEB-INF/AfficherPage.jsp index 9611a3c..1782bbb 100644 --- a/Projet/src/main/webapp/WEB-INF/AfficherPage.jsp +++ b/Projet/src/main/webapp/WEB-INF/AfficherPage.jsp @@ -20,6 +20,7 @@

+ > ${entry.value} > @@ -87,7 +88,87 @@

-

Pas encore de page choisie.

+ +
+
+

+ Rappel des Instructions +

+ +
+

+ + Commandes Slash +

+

Dans un bloc, tapez une commande commençant par / :

+
    +
  • /h1, /h2, /h3 → transforme le bloc en titre de niveau 1, 2 ou 3
  • +
  • /ul → transforme en liste à puces
  • +
  • /ol → transforme en liste numérotée
  • +
  • /code → transforme en bloc de code (optionnel : nom du langage)
  • +
  • /hr → insère une ligne de séparation
  • +
  • /page [titre] → crée une nouvelle page liée avec le titre donné
  • +
+
+ +
+

+ + Markdown en début de bloc (type de contenu) +

+

Si le bloc commence par un de ces symboles :

+
    +
  • # Titre → Titre de niveau 1
  • +
  • ## Sous-titre → Titre de niveau 2
  • +
  • ### Titre 3 → Titre de niveau 3
  • +
  • ``` code → Bloc de code avec mise en forme monospaced
  • +
  • + Élément → Liste à puces
  • +
  • 1. Élément → Liste numérotée
  • +
  • > Citation → Citation classique
  • +
  • >! Attention → Bloc d'alerte/info
  • +
  • >i Note → Bloc d'information
  • +
+
+ +
+

+ + Markdown dans un bloc (mise en forme) +

+
    +
  • `texte`<code> : monospaced/code
  • +
  • **gras**gras
  • +
  • *italique*italique
  • +
  • $$math$$ → active le rendu mathématique (MathJax)
  • +
+
+ +
+

+ + Raccourcis Clavier +

+
    +
  • Entrée : crée un nouveau bloc (hors liste)
  • +
  • Maj + Entrée : nouvelle ligne dans une liste
  • +
+
+ +
+

+ + Fonctionnalités supplémentaires +

+
    +
  • Redimensionnement automatique des blocs
  • +
  • Rendu MathJax pour les formules
  • +
  • Collage propre (suppression du style)
  • +
  • Sauvegarde en temps réel via WebSocket
  • +
+
+ +
+
diff --git a/Projet/src/main/webapp/js/bloc.js b/Projet/src/main/webapp/js/bloc.js index 2efe270..4715902 100644 --- a/Projet/src/main/webapp/js/bloc.js +++ b/Projet/src/main/webapp/js/bloc.js @@ -187,8 +187,13 @@ export function addDeleteBloc(button) { body: params }).then(response => { if (response.ok) { - blocContainer.remove(); // Supprime visuellement le bloc + blocContainer.remove(); ajouterBlocVideSiBesoin(); + + window.socketBloc.send(JSON.stringify({ + action: "deleteBloc", + blocId, + })); } else { console.error("Erreur lors de la suppression du bloc."); } @@ -204,7 +209,7 @@ export function ajouterBlocVideSiBesoin() { const message = document.querySelector('.column.is-half p'); if (message && message.textContent.trim() === "Pas encore de page choisie.") { - return; // Arrête la fonction ici (se passe après avoir supprimé une page) + return; } if (allBlocs.length === 0 || allBlocs[allBlocs.length - 1].innerText.trim() !== "") { diff --git a/Projet/src/main/webapp/js/drag-and-drop.js b/Projet/src/main/webapp/js/drag-and-drop.js index e69de29..091b868 100644 --- a/Projet/src/main/webapp/js/drag-and-drop.js +++ b/Projet/src/main/webapp/js/drag-and-drop.js @@ -0,0 +1,62 @@ +export function initDragAndDrop(containerSelector, itemSelector = '[draggable]') { + const container = document.querySelector(containerSelector); + let draggedElement = null; + + if (!container) { + return; + } + + container.addEventListener('dragstart', e => { + if (e.target.matches(itemSelector)) { + draggedElement = e.target; + e.dataTransfer.effectAllowed = 'move'; + e.target.classList.add('dragging'); + } + }); + + container.addEventListener('dragover', e => { + e.preventDefault(); + const target = e.target.closest(itemSelector); + if (target && draggedElement && target !== draggedElement) { + container.insertBefore(draggedElement, target); + } + }); + + container.addEventListener('drop', (e) => { + e.preventDefault(); + }); + + container.addEventListener('dragend', () => { + if (draggedElement) { + draggedElement.classList.remove('dragging'); + draggedElement = null; + } + + const blocs = Array.from(container.querySelectorAll(itemSelector)); + const ordre = blocs.map((bloc, index) => { + const editor = bloc.querySelector('.editor'); + return { + id: editor.dataset.id, + ordre: index + 1 + }; + }); + + fetch('SauvegarderOrdre', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(ordre) + }).then(response => { + if (response.ok) { + const modifOrdre = { + action: "modifierOrdreBloc", + order: ordre.map(b => b.id) + }; + window.socketBloc.send(JSON.stringify(modifOrdre)); + } else { + console.error("Erreur lors de la modification de l'ordre des blocs."); + } + }); + }); +} diff --git a/Projet/src/main/webapp/js/functionsBloc.js b/Projet/src/main/webapp/js/functionsBloc.js index ca12a12..fbe1c90 100644 --- a/Projet/src/main/webapp/js/functionsBloc.js +++ b/Projet/src/main/webapp/js/functionsBloc.js @@ -71,6 +71,7 @@ export function renderBlocStyle(bloc) { break; case 'PAGE': + renderPage(bloc); bloc.classList.add('is-page-link'); break; @@ -112,6 +113,18 @@ export function renderListe(bloc) { bloc.innerHTML = `<${tag}>${items}`; } +export function renderPage(bloc){ + const metadata = getBlocDataMetadata(bloc); + const pageId = metadata.pageId; + + if (!pageId) return; + + const lien = `AfficherPage?id=${pageId}`; + const titre = metadata.title; + + bloc.innerHTML = `${titre}`; +} + export function handleSlashCommand(bloc, texte) { return new Promise((resolve) => { const parts = texte.trim().substring(1).split(" "); @@ -172,7 +185,7 @@ export function handleSlashCommand(bloc, texte) { applyBlocType(bloc, "SEPARATEUR", {}); // ou un type adapté selon ta logique bloc.innerText = ' '; autoResize(bloc); - resolve(true); + resolve(); break; default: @@ -189,7 +202,7 @@ export function handleSlashCommand(bloc, texte) { export async function handleMarkdownSyntax(bloc, texte) { // Vérifier si c'est un titre if (texte.startsWith('#')) { - const level = texte.split(' ')[0].length; // Compter le nombre de # pour le niveau du titre + const level = texte.split(' ')[0].length; if (level >= 1 && level <= 6) { applyBlocType(bloc, "TITRE", { level: level }); bloc.textContent = texte.replace(/^#+\s*/, ''); @@ -253,9 +266,7 @@ export function setBlocMetadata(bloc, metadata) { const citationTypes = { '>!': 'danger', '>i': 'info', -/* '>w': 'warning', - '>s': 'success',*/ - '>': 'normal' // doit rester en dernier + '>': 'normal' }; function detectCitation(texte) { diff --git a/Projet/src/main/webapp/js/main.js b/Projet/src/main/webapp/js/main.js index 7aa11ba..07fb2ad 100644 --- a/Projet/src/main/webapp/js/main.js +++ b/Projet/src/main/webapp/js/main.js @@ -2,6 +2,7 @@ import { initBlocs, ajouterBlocVideSiBesoin, focusDernierBloc } from './bloc.js' import { initTchat } from './tchat.js'; import { initPages } from './page.js'; import { initSocketBloc } from './socket-bloc.js'; +import { initDragAndDrop } from './drag-and-drop.js'; window.addEventListener('DOMContentLoaded', () => { initTchat(); @@ -10,6 +11,7 @@ window.addEventListener('DOMContentLoaded', () => { initBlocs(); ajouterBlocVideSiBesoin(); focusDernierBloc(); + initDragAndDrop('#md', '.bloc-container'); document.body.addEventListener('click', function(event) { // Vérifie si l'élément cliqué est un lien dans un .editor diff --git a/Projet/src/main/webapp/js/socket-bloc.js b/Projet/src/main/webapp/js/socket-bloc.js index 6cda2b9..8aca6bb 100644 --- a/Projet/src/main/webapp/js/socket-bloc.js +++ b/Projet/src/main/webapp/js/socket-bloc.js @@ -1,5 +1,4 @@ import { autoResize, creerBlocDOM } from './utils.js' -import { renderBlocStyle, renderListe } from './functionsBloc.js' export function initSocketBloc() { const params = new URLSearchParams(window.location.search); @@ -21,7 +20,7 @@ export function initSocketBloc() { const data = JSON.parse(event.data); if (data.action === "update") { - const blocElement = document.querySelector(`.editor[data-id='${data.blocId}']`); + const blocElement = document.querySelector(`[data-id="${data.blocId}"`); if (blocElement) { blocElement.innerHTML = data.content; blocElement.setAttribute('data-metadata', data.metadata); @@ -46,7 +45,7 @@ export function initSocketBloc() { ordre: data.ordre }); } else if (data.action === "actualiserMiseEnPageBloc") { - const blocElement = document.querySelector(`.editor[data-id='${data.blocId}']`); + const blocElement = document.querySelector(`[data-id="${data.blocId}"`); if (blocElement) { blocElement.dataset.type = data.type; blocElement.innerHTML = data.content; @@ -54,6 +53,28 @@ export function initSocketBloc() { blocElement.className = data.classBloc; renderBloc(blocElement, blocElement.dataset.metadata); } + } else if (data.action === "modifierOrdreBloc") { + const container = document.getElementById("md"); + const order = data.order; + order.forEach(blocId => { + + const blocElement = document.querySelector(`[data-id="${blocId}"]`); + if (blocElement) { + const blocContainer = blocElement.closest('.bloc-container'); + if (blocContainer) { + container.appendChild(blocContainer); + } + } + }); + } else if (data.action === "deleteBloc") { + const blocId = data.blocId; + const blocElement = document.querySelector(`[data-id="${blocId}"]`); + if (blocElement) { + const blocContainer = blocElement.closest('.bloc-container'); + if (blocContainer) { + blocContainer.remove(); + } + } } }; diff --git a/Projet/src/main/webapp/js/utils.js b/Projet/src/main/webapp/js/utils.js index c72a554..f7281e1 100644 --- a/Projet/src/main/webapp/js/utils.js +++ b/Projet/src/main/webapp/js/utils.js @@ -23,6 +23,7 @@ export function placeCursorAtEnd(element) { export function creerBlocDOM({ blocId, content = "", type = "TEXTE", metadata = "{}", ordre = null }) { const container = document.createElement('div'); container.classList.add('field', 'is-grouped', 'is-align-items-flex-start', 'bloc-container'); + container.setAttribute('draggable', 'true'); const control = document.createElement('div'); control.classList.add('control', 'is-expanded'); diff --git a/Projet/src/main/webapp/styles.css b/Projet/src/main/webapp/styles.css index 5d8b2f7..edb0c4f 100644 --- a/Projet/src/main/webapp/styles.css +++ b/Projet/src/main/webapp/styles.css @@ -89,3 +89,7 @@ background-color: #eaf6fb; color: #2980b9; } + +.dragging { + opacity: 0.5; +}