Ajout déplacement blocs

This commit is contained in:
Lensors 2025-05-11 10:06:25 +02:00
parent 9277154e0c
commit 8d07c38d32
12 changed files with 366 additions and 24 deletions

View File

@ -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 { try {
Connection connexion = DriverManager.getConnection(bdURL, bdLogin, bdPassword); Connection connexion = DriverManager.getConnection(bdURL, bdLogin, bdPassword);
String sql = " DELETE FROM bloc" String sql = " DELETE FROM bloc"
@ -209,8 +210,32 @@ public class Bloc extends ParamBD {
pst.setInt(2, idU); pst.setInt(2, idU);
pst.executeUpdate(); pst.executeUpdate();
pst.close();
connexion.close();
reponse = true;
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace(); 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();
}
} }
} }

View File

@ -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\"}");
}
}
}

View File

@ -29,22 +29,35 @@ public class SupprimerBloc extends HttpServlet {
} }
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
HttpSession session = request.getSession(); HttpSession session = request.getSession();
Utilisateur u = (Utilisateur) session.getAttribute("utilisateur"); Utilisateur u = (Utilisateur) session.getAttribute("utilisateur");
if(u != null) { if (u == null) {
String idBlocStr = request.getParameter("blocId"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("{\"error\": \"Non autorisé\"}");
return;
}
if(idBlocStr == null || idBlocStr.isEmpty()) { String idBlocStr = request.getParameter("blocId");
response.sendRedirect("AfficherPage");
} else {
int idBloc = Integer.parseInt(idBlocStr);
Bloc.supprimerBloc(idBloc, u.getId());
response.sendRedirect("AfficherPage");
}
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 { } else {
response.sendRedirect("/Projet/"); response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write("{\"error\": \"Suppression interdite\"}");
} }
} }
} }

View File

@ -6,6 +6,37 @@
<jsp:param name="titre" value="Accueil" /> <jsp:param name="titre" value="Accueil" />
</jsp:include> </jsp:include>
<div class="section">
<div class="container">
<div class="columns is-centered">
<div class="column is-two-thirds">
<div class="box has-background-light">
<h1 class="title is-3 icon-text">
<span class="icon has-text-info"><i class="fas fa-pencil-alt"></i></span>
<span>Bienvenue sur notre plateforme de prise de notes collaborative</span>
</h1>
<p class="content">
Ce site vous permet de créer facilement des pages de contenu interactives en utilisant du <strong>Markdown</strong>, dorganiser vos idées en blocs, et de collaborer en temps réel avec dautres utilisateurs.
</p>
<ul>
<li><strong>Éditeur intelligent :</strong> avec support des titres, listes, citations, et mise en forme automatique.</li>
<li><strong>Partage de pages :</strong> invitez vos collaborateurs avec un accès en lecture ou en édition.</li>
<li><strong>Sauvegarde en temps réel :</strong> vos modifications sont automatiquement enregistrées et partagées.</li>
</ul>
<p class="content mt-5">
Une fois votre travail terminé, vous pouvez <strong>exporter vos pages</strong> dans plusieurs formats :
</p>
<ul>
<li>HTML — pour une intégration facile sur le web</li>
<li>LaTeX — idéal pour des documents scientifiques ou académiques</li>
<li>Markdown — pour garder un format brut et portable</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<c:set var="erreur" value="${erreur}" /> <c:set var="erreur" value="${erreur}" />

View File

@ -20,6 +20,7 @@
<c:when test="${not empty page.titre}"> <c:when test="${not empty page.titre}">
<div class="is-flex is-justify-content-space-between is-align-items mb-4"> <div class="is-flex is-justify-content-space-between is-align-items mb-4">
<h2 class="block"> <h2 class="block">
<a href = "AfficherPage"><i class="fa-solid fa-house-chimney"></i></a> >
<c:forEach var="entry" items="${hierarchie}"> <c:forEach var="entry" items="${hierarchie}">
<a href="AfficherPage?id=${entry.key}">${entry.value}</a> > <a href="AfficherPage?id=${entry.key}">${entry.value}</a> >
</c:forEach> </c:forEach>
@ -87,7 +88,87 @@
</div> </div>
</c:when> </c:when>
<c:otherwise> <c:otherwise>
<p>Pas encore de page choisie.</p> <a href = "AfficherPage"><i class="fa-solid fa-house-chimney"></i></a>
<section class="section">
<div class="container">
<h1 class="title is-3 icon-text">
<span>Rappel des Instructions</span>
</h1>
<div class="box">
<h2 class="title is-4 icon-text">
<span class="icon has-text-info"><i class="fas fa-terminal"></i></span>
<span>Commandes Slash</span>
</h2>
<p>Dans un bloc, tapez une commande commençant par <code>/</code> :</p>
<ul>
<li><code>/h1</code>, <code>/h2</code>, <code>/h3</code> → transforme le bloc en titre de niveau 1, 2 ou 3</li>
<li><code>/ul</code> → transforme en liste à puces</li>
<li><code>/ol</code> → transforme en liste numérotée</li>
<li><code>/code</code> → transforme en bloc de code (optionnel : nom du langage)</li>
<li><code>/hr</code> → insère une ligne de séparation</li>
<li><code>/page [titre]</code> → crée une nouvelle page liée avec le titre donné</li>
</ul>
</div>
<div class="box">
<h2 class="title is-4 icon-text">
<span class="icon has-text-link"><i class="fas fa-heading"></i></span>
<span>Markdown <em>en début de bloc</em> (type de contenu)</span>
</h2>
<p>Si le bloc commence par un de ces symboles :</p>
<ul>
<li><code># Titre</code> → Titre de niveau 1</li>
<li><code>## Sous-titre</code> → Titre de niveau 2</li>
<li><code>### Titre 3</code> → Titre de niveau 3</li>
<li><code>``` code </code> → Bloc de code avec mise en forme monospaced</li>
<li><code>+ Élément</code> → Liste à puces</li>
<li><code>1. Élément</code> → Liste numérotée</li>
<li><code>&gt; Citation</code> → Citation classique</li>
<li><code>&gt;! Attention</code> → Bloc d'alerte/info</li>
<li><code>&gt;i Note</code> → Bloc d'information</li>
</ul>
</div>
<div class="box">
<h2 class="title is-4 icon-text">
<span class="icon has-text-warning"><i class="fas fa-pen-fancy"></i></span>
<span>Markdown <em>dans un bloc</em> (mise en forme)</span>
</h2>
<ul>
<li><code>`texte`</code> → <code>&lt;code&gt;</code> : monospaced/code</li>
<li><code>**gras**</code> → <strong>gras</strong></li>
<li><code>*italique*</code> → <em>italique</em></li>
<li><code>$$math$$ </code> → active le rendu mathématique (MathJax)</li>
</ul>
</div>
<div class="box">
<h2 class="title is-4 icon-text">
<span class="icon has-text-success"><i class="fas fa-keyboard"></i></span>
<span>Raccourcis Clavier</span>
</h2>
<ul>
<li><strong>Entrée</strong> : crée un nouveau bloc (hors liste)</li>
<li><strong>Maj + Entrée</strong> : nouvelle ligne dans une liste</li>
</ul>
</div>
<div class="box">
<h2 class="title is-4 icon-text">
<span class="icon has-text-danger"><i class="fas fa-tools"></i></span>
<span>Fonctionnalités supplémentaires</span>
</h2>
<ul>
<li>Redimensionnement automatique des blocs</li>
<li>Rendu MathJax pour les formules</li>
<li>Collage propre (suppression du style)</li>
<li>Sauvegarde en temps réel via WebSocket</li>
</ul>
</div>
</div>
</section>
</c:otherwise> </c:otherwise>
</c:choose> </c:choose>
</div> </div>

View File

@ -187,8 +187,13 @@ export function addDeleteBloc(button) {
body: params body: params
}).then(response => { }).then(response => {
if (response.ok) { if (response.ok) {
blocContainer.remove(); // Supprime visuellement le bloc blocContainer.remove();
ajouterBlocVideSiBesoin(); ajouterBlocVideSiBesoin();
window.socketBloc.send(JSON.stringify({
action: "deleteBloc",
blocId,
}));
} else { } else {
console.error("Erreur lors de la suppression du bloc."); console.error("Erreur lors de la suppression du bloc.");
} }
@ -204,7 +209,7 @@ export function ajouterBlocVideSiBesoin() {
const message = document.querySelector('.column.is-half p'); const message = document.querySelector('.column.is-half p');
if (message && message.textContent.trim() === "Pas encore de page choisie.") { 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() !== "") { if (allBlocs.length === 0 || allBlocs[allBlocs.length - 1].innerText.trim() !== "") {

View File

@ -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.");
}
});
});
}

View File

@ -71,6 +71,7 @@ export function renderBlocStyle(bloc) {
break; break;
case 'PAGE': case 'PAGE':
renderPage(bloc);
bloc.classList.add('is-page-link'); bloc.classList.add('is-page-link');
break; break;
@ -112,6 +113,18 @@ export function renderListe(bloc) {
bloc.innerHTML = `<${tag}>${items}</${tag}>`; bloc.innerHTML = `<${tag}>${items}</${tag}>`;
} }
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 = `<a href="${lien}">${titre}</a>`;
}
export function handleSlashCommand(bloc, texte) { export function handleSlashCommand(bloc, texte) {
return new Promise((resolve) => { return new Promise((resolve) => {
const parts = texte.trim().substring(1).split(" "); 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 applyBlocType(bloc, "SEPARATEUR", {}); // ou un type adapté selon ta logique
bloc.innerText = ' '; bloc.innerText = ' ';
autoResize(bloc); autoResize(bloc);
resolve(true); resolve();
break; break;
default: default:
@ -189,7 +202,7 @@ export function handleSlashCommand(bloc, texte) {
export async function handleMarkdownSyntax(bloc, texte) { export async function handleMarkdownSyntax(bloc, texte) {
// Vérifier si c'est un titre // Vérifier si c'est un titre
if (texte.startsWith('#')) { 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) { if (level >= 1 && level <= 6) {
applyBlocType(bloc, "TITRE", { level: level }); applyBlocType(bloc, "TITRE", { level: level });
bloc.textContent = texte.replace(/^#+\s*/, ''); bloc.textContent = texte.replace(/^#+\s*/, '');
@ -253,9 +266,7 @@ export function setBlocMetadata(bloc, metadata) {
const citationTypes = { const citationTypes = {
'>!': 'danger', '>!': 'danger',
'>i': 'info', '>i': 'info',
/* '>w': 'warning', '>': 'normal'
'>s': 'success',*/
'>': 'normal' // doit rester en dernier
}; };
function detectCitation(texte) { function detectCitation(texte) {

View File

@ -2,6 +2,7 @@ import { initBlocs, ajouterBlocVideSiBesoin, focusDernierBloc } from './bloc.js'
import { initTchat } from './tchat.js'; import { initTchat } from './tchat.js';
import { initPages } from './page.js'; import { initPages } from './page.js';
import { initSocketBloc } from './socket-bloc.js'; import { initSocketBloc } from './socket-bloc.js';
import { initDragAndDrop } from './drag-and-drop.js';
window.addEventListener('DOMContentLoaded', () => { window.addEventListener('DOMContentLoaded', () => {
initTchat(); initTchat();
@ -10,6 +11,7 @@ window.addEventListener('DOMContentLoaded', () => {
initBlocs(); initBlocs();
ajouterBlocVideSiBesoin(); ajouterBlocVideSiBesoin();
focusDernierBloc(); focusDernierBloc();
initDragAndDrop('#md', '.bloc-container');
document.body.addEventListener('click', function(event) { document.body.addEventListener('click', function(event) {
// Vérifie si l'élément cliqué est un lien dans un .editor // Vérifie si l'élément cliqué est un lien dans un .editor

View File

@ -1,5 +1,4 @@
import { autoResize, creerBlocDOM } from './utils.js' import { autoResize, creerBlocDOM } from './utils.js'
import { renderBlocStyle, renderListe } from './functionsBloc.js'
export function initSocketBloc() { export function initSocketBloc() {
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
@ -21,7 +20,7 @@ export function initSocketBloc() {
const data = JSON.parse(event.data); const data = JSON.parse(event.data);
if (data.action === "update") { if (data.action === "update") {
const blocElement = document.querySelector(`.editor[data-id='${data.blocId}']`); const blocElement = document.querySelector(`[data-id="${data.blocId}"`);
if (blocElement) { if (blocElement) {
blocElement.innerHTML = data.content; blocElement.innerHTML = data.content;
blocElement.setAttribute('data-metadata', data.metadata); blocElement.setAttribute('data-metadata', data.metadata);
@ -46,7 +45,7 @@ export function initSocketBloc() {
ordre: data.ordre ordre: data.ordre
}); });
} else if (data.action === "actualiserMiseEnPageBloc") { } else if (data.action === "actualiserMiseEnPageBloc") {
const blocElement = document.querySelector(`.editor[data-id='${data.blocId}']`); const blocElement = document.querySelector(`[data-id="${data.blocId}"`);
if (blocElement) { if (blocElement) {
blocElement.dataset.type = data.type; blocElement.dataset.type = data.type;
blocElement.innerHTML = data.content; blocElement.innerHTML = data.content;
@ -54,6 +53,28 @@ export function initSocketBloc() {
blocElement.className = data.classBloc; blocElement.className = data.classBloc;
renderBloc(blocElement, blocElement.dataset.metadata); 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();
}
}
} }
}; };

View File

@ -23,6 +23,7 @@ export function placeCursorAtEnd(element) {
export function creerBlocDOM({ blocId, content = "", type = "TEXTE", metadata = "{}", ordre = null }) { export function creerBlocDOM({ blocId, content = "", type = "TEXTE", metadata = "{}", ordre = null }) {
const container = document.createElement('div'); const container = document.createElement('div');
container.classList.add('field', 'is-grouped', 'is-align-items-flex-start', 'bloc-container'); container.classList.add('field', 'is-grouped', 'is-align-items-flex-start', 'bloc-container');
container.setAttribute('draggable', 'true');
const control = document.createElement('div'); const control = document.createElement('div');
control.classList.add('control', 'is-expanded'); control.classList.add('control', 'is-expanded');

View File

@ -89,3 +89,7 @@
background-color: #eaf6fb; background-color: #eaf6fb;
color: #2980b9; color: #2980b9;
} }
.dragging {
opacity: 0.5;
}