The last one before the soutenance

This commit is contained in:
Lensors 2025-05-19 16:58:17 +02:00
parent 8d07c38d32
commit 25e19ec12e
10 changed files with 243 additions and 53 deletions

View File

@ -10,6 +10,7 @@ import jakarta.servlet.http.HttpSession;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import java.util.Map; import java.util.Map;
@WebServlet("/AfficherPage") @WebServlet("/AfficherPage")
@ -35,6 +36,10 @@ public class AfficherPage extends HttpServlet {
request.setAttribute("listeMessages", listeMessages); request.setAttribute("listeMessages", listeMessages);
request.setAttribute("listeUtilisateurs", listeUtilisateurs); request.setAttribute("listeUtilisateurs", listeUtilisateurs);
String menuHtml = genererMenuHTML(u.getId());
request.setAttribute("menuHtml", menuHtml);
if (idStr != null ) { if (idStr != null ) {
try { try {
int id = Integer.parseInt(idStr); int id = Integer.parseInt(idStr);
@ -67,4 +72,41 @@ public class AfficherPage extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
} }
public static String genererMenuHTML(int idU) {
Map<Integer, List<Page>> arbre = Page.getArbreDesPages(idU);
StringBuilder html = new StringBuilder();
html.append("<ul>\n");
construireHTMLRecursif(arbre, -1, html, 6);
html.append(" </ul>\n");
return html.toString();
}
private static void construireHTMLRecursif(Map<Integer, List<Page>> arbre, int parentId, StringBuilder html, int indentLevel) {
List<Page> enfants = arbre.get(parentId);
if (enfants == null || enfants.isEmpty()) return;
String indent = "\t".repeat(indentLevel);
for (Page p : enfants) {
List<Page> sousPages = arbre.get(p.getId());
boolean aSousPages = sousPages != null && !sousPages.isEmpty();
html.append(indent).append("<li>\n");
html.append(indent).append("\t<div class='is-flex is-align-items-center is-justify-content-space-between'>\n");
html.append(indent).append("\t\t<a href='AfficherPage?id=").append(p.getId()).append("'>")
.append(p.getTitre()).append("</a>\n");
html.append(indent).append("\t\t<button class='delete delete-page-btn has-background-danger ml-2' data-id='")
.append(p.getId()).append("'></button>\n");
html.append(indent).append("\t</div>\n");
if (aSousPages) {
html.append(indent).append("\t<ul class='ml-4'>\n");
construireHTMLRecursif(arbre, p.getId(), html, indentLevel + 1);
html.append(indent).append("\t</ul>\n");
}
html.append(indent).append("</li>\n");
}
}
} }

View File

@ -9,7 +9,9 @@ import java.sql.Statement;
import java.time.LocalDate; import java.time.LocalDate;
import java.sql.Date; import java.sql.Date;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import projet.Bloc.Type; import projet.Bloc.Type;
@ -283,4 +285,57 @@ public class Page extends ParamBD {
e.printStackTrace(); e.printStackTrace();
} }
} }
public static Map<Integer, List<Page>> getArbreDesPages(int idU) {
Map<Integer, List<Page>> arbre = new HashMap<>();
chargerSousPagesRecursivement(idU, arbre, null);
return arbre;
}
public static void chargerSousPagesRecursivement(int idU, Map<Integer, List<Page>> arbre, Integer idParent) {
List<Page> enfants = new ArrayList<>();
try {
Connection connexion = DriverManager.getConnection(bdURL, bdLogin, bdPassword);
String sql;
PreparedStatement pst;
if(idParent == null) {
sql = " SELECT id, titre"
+ " FROM page "
+ "WHERE auteur_id = ? AND page_parent_id IS NULL"
+ ";";
pst = connexion.prepareStatement(sql);
pst.setInt(1, idU);
} else {
sql = " SELECT id, titre"
+ " FROM page "
+ "WHERE auteur_id = ? AND page_parent_id = ?"
+ ";";
pst = connexion.prepareStatement(sql);
pst.setInt(1, idU);
pst.setInt(2, idParent);
}
ResultSet rs = pst.executeQuery();
while (rs.next()) {
int id = rs.getInt("id");
String titre = rs.getString("titre");
Page page = new Page(id, idU, titre);
enfants.add(page);
}
rs.close();
connexion.close();
} catch (SQLException e) {
e.printStackTrace();
}
int key = (idParent == null) ? -1 : idParent;
arbre.put(key, enfants);
for (Page enfant : enfants) {
chargerSousPagesRecursivement(idU, arbre, enfant.getId());
}
}
} }

View File

@ -7,15 +7,15 @@
</jsp:include> </jsp:include>
<div class="columns"> <div class="columns is-flex" style="transition: all 0.3s ease;">
<!-- La colonne pour le menu des pages --> <!-- La colonne pour le menu des pages -->
<div class="column is-one-fifth"> <div class="column is-one-fifth" id="menuColumn">
<jsp:include page="MenuPages.jsp" /> <jsp:include page="MenuPages.jsp" />
</div> </div>
<!-- La colonne pour la page choisie --> <!-- La colonne pour la page choisie -->
<div class="column is-half"> <div class="column flex-main" id="mainColumn">
<c:choose> <c:choose>
<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">
@ -53,7 +53,7 @@
</span> </span>
</button> </button>
</div> </div>
<div class="dropdown-menu" id="dropdown-menu3" role="menu"> <div class="dropdown-menu" id="dropdown-menu4" role="menu">
<div class="dropdown-content"> <div class="dropdown-content">
<c:forEach var="u" items="${listeUtilisateurs}"> <c:forEach var="u" items="${listeUtilisateurs}">
<c:if test="${u.id != utilisateur.id}"> <c:if test="${u.id != utilisateur.id}">
@ -67,7 +67,7 @@
</div> </div>
<div class="block" id="md"> <div class="block" id="md">
<c:forEach var="bloc" items="${page.listeBlocs}"> <c:forEach var="bloc" items="${page.listeBlocs}">
<div class="field is-grouped is-align-items-flex-start bloc-container" draggable="true"> <div class="field is-grouped is-align-items-flex-start bloc-container hover-bloc" draggable="true">
<div class="control is-expanded"> <div class="control is-expanded">
<div <div
class="is-primary editor" class="is-primary editor"
@ -139,6 +139,7 @@
<li><code>`texte`</code> → <code>&lt;code&gt;</code> : monospaced/code</li> <li><code>`texte`</code> → <code>&lt;code&gt;</code> : monospaced/code</li>
<li><code>**gras**</code> → <strong>gras</strong></li> <li><code>**gras**</code> → <strong>gras</strong></li>
<li><code>*italique*</code> → <em>italique</em></li> <li><code>*italique*</code> → <em>italique</em></li>
<li><code>$math$</code> → active le rendu mathématique en ligne (MathJax)</li>
<li><code>$$math$$ </code> → active le rendu mathématique (MathJax)</li> <li><code>$$math$$ </code> → active le rendu mathématique (MathJax)</li>
</ul> </ul>
</div> </div>
@ -174,10 +175,10 @@
</div> </div>
<!-- La colonne pour le Tchat --> <!-- La colonne pour le Tchat -->
<div class="column is-one-quarter"> <div class="column flex-tchat slide-out" id="tchatColumn">
<jsp:include page="Tchat.jsp" /> <jsp:include page="Tchat.jsp" />
</div> </div>
</div> </div>
<button class="button is-small is-info vertical-toggle" id="toggleTchatBtn">Afficher / Cacher le Tchat</button>
<jsp:include page="Footer.jsp" /> <jsp:include page="Footer.jsp" />

View File

@ -8,15 +8,23 @@
<link href="bulma.css" rel="stylesheet"> <link href="bulma.css" rel="stylesheet">
<link rel="stylesheet" href="styles.css"> <link rel="stylesheet" href="styles.css">
<script src="https://kit.fontawesome.com/39474be7e2.js" crossorigin="anonymous"></script> <script src="https://kit.fontawesome.com/39474be7e2.js" crossorigin="anonymous"></script>
<script type="text/javascript" async <script>
src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"> window.MathJax = {
tex: {
inlineMath: [['$', '$'], ['\\(', '\\)']]
},
svg: {
fontCache: 'global'
}
};
</script> </script>
<script async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
</head> </head>
<body> <body>
<nav class="navbar has-shadow is-white" aria-label="main navigation"> <nav class="navbar has-shadow is-white" aria-label="main navigation">
<div class="navbar-brand"> <div class="navbar-brand">
<a class="navbar-item" href="/Projet/"> <a class="navbar-item" href="/Projet/">
<h1 class="title is-2">Prise de notes collaborative</h1> <h1 class="title is-2 has-text-success">Prise de notes collaborative</h1>
</a> </a>
</div> </div>
<div class="navbar-menu"> <div class="navbar-menu">

View File

@ -4,15 +4,9 @@
<h2 class="block">Menu des pages</h2> <h2 class="block">Menu des pages</h2>
<aside class="menu"> <aside class="menu">
<ul class="menu-list" id="menuPages"> <div>
<c:forEach var="page" items="${listePages}"> ${menuHtml}
<li> </div>
<button class="delete delete-page-btn is-pulled-right has-background-danger" data-id="${page.id}"></button>
<a href="AfficherPage?id=${page.id}">${page.titre}</a>
</li>
</c:forEach>
</ul>
<hr> <hr>
<ul class="menu-list"> <ul class="menu-list">
<c:forEach var="pagePartagees" items="${listePagesPartagees}"> <c:forEach var="pagePartagees" items="${listePagesPartagees}">

View File

@ -20,13 +20,19 @@ export function initBlocs() {
}); });
}); });
document.querySelectorAll('.delete-bloc-btn').forEach(t => { document.querySelectorAll('.delete-bloc-btn').forEach(btn => {
addDeleteBloc(t); addDeleteBloc(btn);
btn.addEventListener('mouseenter', () => {
btn.closest('.hover-bloc').classList.add('hovered');
});
btn.addEventListener('mouseleave', () => {
btn.closest('.hover-bloc').classList.remove('hovered');
});
}); });
formatEditorContent(); formatEditorContent();
document.querySelectorAll('#md [contenteditable="true"]').forEach(bloc => { document.querySelectorAll('#md ').forEach(bloc => {
autoResize(bloc); autoResize(bloc);
}); });
@ -169,9 +175,9 @@ export function addDeleteBloc(button) {
button.addEventListener('click', function() { button.addEventListener('click', function() {
const blocId = button.dataset.id; const blocId = button.dataset.id;
const blocContainer = button.closest('.bloc-container'); const blocContainer = button.closest('.bloc-container');
const bloc = blocContainer.querySelector('[contenteditable="true"]'); const bloc = blocContainer.querySelector('.editor');
if (bloc.dataset.type != "SEPARATEUR" && bloc.innerText === "") { if (bloc.dataset.type != "SEPARATEUR" && bloc.innerText.trim() === "") {
return; return;
} }
@ -203,7 +209,10 @@ export function addDeleteBloc(button) {
} }
export function ajouterBlocVideSiBesoin() { export function ajouterBlocVideSiBesoin() {
const allBlocs = document.querySelectorAll('#md [contenteditable="true"]'); const container = document.querySelector('#md');
if (!container) return;
const allBlocs = container.querySelectorAll('.editor');
const pageId = new URLSearchParams(window.location.search).get("id"); const pageId = new URLSearchParams(window.location.search).get("id");
if (pageId === null) return; if (pageId === null) return;
@ -212,7 +221,7 @@ export function ajouterBlocVideSiBesoin() {
return; return;
} }
if (allBlocs.length === 0 || allBlocs[allBlocs.length - 1].innerText.trim() !== "") { if (allBlocs.length === 0 || allBlocs[allBlocs.length - 1].innerText.trim() !== "" || allBlocs[allBlocs.length - 1].dataset.type === "SEPARATEUR") {
const params = new URLSearchParams(); const params = new URLSearchParams();
params.append("contenu", ""); // Bloc vide params.append("contenu", ""); // Bloc vide
params.append("type", "TEXTE"); // Type par défaut : TEXTE params.append("type", "TEXTE"); // Type par défaut : TEXTE
@ -227,30 +236,30 @@ export function ajouterBlocVideSiBesoin() {
}, },
body: params.toString() body: params.toString()
}).then(response => response.json()) }).then(response => response.json())
.then(data => { .then(data => {
if (data && data.idGenere) { if (data && data.idGenere) {
const idGenere = data.idGenere; const idGenere = data.idGenere;
const newBloc = creerBlocDOM({ const newBloc = creerBlocDOM({
blocId: idGenere, blocId: idGenere,
content: "", content: "",
type: "TEXTE", type: "TEXTE",
metadata: "{}", metadata: "{}",
ordre: allBlocs.length ordre: allBlocs.length
}); });
newBloc.focus(); newBloc.focus();
// Envoi d'une notification d'un nouveau bloc créé via websocket // Envoi d'une notification d'un nouveau bloc créé via websocket
window.socketBloc.send(JSON.stringify({ window.socketBloc.send(JSON.stringify({
action: "newBloc", action: "newBloc",
blocId: idGenere, blocId: idGenere,
content: "", content: "",
type: "TEXTE", type: "TEXTE",
metadata: "{}", metadata: "{}",
ordre: allBlocs.length ordre: allBlocs.length
})); }));
} }
}); });
} }
} }

View File

@ -73,6 +73,7 @@ export function renderBlocStyle(bloc) {
case 'PAGE': case 'PAGE':
renderPage(bloc); renderPage(bloc);
bloc.classList.add('is-page-link'); bloc.classList.add('is-page-link');
bloc.contentEditable = "false";
break; break;
case 'SEPARATEUR': case 'SEPARATEUR':
@ -183,7 +184,7 @@ export function handleSlashCommand(bloc, texte) {
case "hr": case "hr":
applyBlocType(bloc, "SEPARATEUR", {}); // ou un type adapté selon ta logique applyBlocType(bloc, "SEPARATEUR", {}); // ou un type adapté selon ta logique
bloc.innerText = ' '; bloc.textContent = '';
autoResize(bloc); autoResize(bloc);
resolve(); resolve();
break; break;

View File

@ -1,5 +1,5 @@
import { initBlocs, ajouterBlocVideSiBesoin, focusDernierBloc } from './bloc.js'; import { initBlocs, ajouterBlocVideSiBesoin, focusDernierBloc } from './bloc.js';
import { initTchat } from './tchat.js'; import { initTchat, toggleTchat } 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'; import { initDragAndDrop } from './drag-and-drop.js';
@ -22,4 +22,23 @@ window.addEventListener('DOMContentLoaded', () => {
window.location.href = url; // Rediriger manuellement window.location.href = url; // Rediriger manuellement
} }
}); });
document.querySelectorAll('.item-header .toggle').forEach(toggle => {
toggle.addEventListener('click', function (e) {
// Empêche le lien de se propager si cliqué
e.stopPropagation();
const li = this.closest('li');
const sublist = li.querySelector('ul');
if (sublist) {
sublist.classList.toggle('hidden');
this.classList.toggle('rotated');
}
});
});
document.getElementById('toggleTchatBtn').addEventListener('click', toggleTchat);
}); });

View File

@ -22,3 +22,22 @@ export function initTchat() {
}); });
} }
} }
export function toggleTchat() {
const tchat = document.getElementById('tchatColumn');
const main = document.getElementById('mainColumn');
if (tchat.classList.contains('slide-in')) {
tchat.classList.remove('slide-in');
tchat.classList.add('slide-out');
main.classList.remove('collapsed');
main.classList.add('expanded');
} else {
tchat.classList.remove('slide-out');
tchat.classList.add('slide-in');
main.classList.remove('expanded');
main.classList.add('collapsed');
}
}

View File

@ -93,3 +93,45 @@
.dragging { .dragging {
opacity: 0.5; opacity: 0.5;
} }
/* Animation pour glisser la colonne vers la droite */
.slide-out {
transform: translateX(100%);
opacity: 0;
transition: transform 0.3s ease, opacity 0.3s ease;
}
/* Animation pour la faire apparaître */
.slide-in {
transform: translateX(0%);
opacity: 1;
transition: transform 0.3s ease, opacity 0.3s ease;
}
.flex-main {
flex-grow: 4;
transition: flex-grow 0.3s ease;
}
.expanded {
flex-grow: 4;
}
.collapsed {
flex-grow: 2;
}
.vertical-toggle {
position: fixed;
top: 50%;
right: 0;
transform: rotate(-90deg) translateY(-50%);
transform-origin: right center;
z-index: 999;
border-radius: 6px 6px 0 0;
}
.hover-bloc.hovered {
background-color: #ffe6e6;
transition: background-color 0.2s ease;
}