Ajout bloc collaborative + début mardown
Change-Id: Id18543aaa87518f9e3fbb33f61f11192659e7b8b Signed-off-by: Lensors <matribe@matribe.fr>
This commit is contained in:
parent
67ccdd664b
commit
873432327d
|
|
@ -0,0 +1,49 @@
|
||||||
|
#Structure Générale de metadata
|
||||||
|
{
|
||||||
|
"type": "TITRE",
|
||||||
|
"metadata": {
|
||||||
|
... // dépend du type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Détail par type
|
||||||
|
|
||||||
|
##TEXTE
|
||||||
|
{
|
||||||
|
"type": "TEXTE",
|
||||||
|
"metadata": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
##TITRE
|
||||||
|
{
|
||||||
|
"type": "TITRE",
|
||||||
|
"metadata": {
|
||||||
|
"level": 1 // ou 2, 3...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
##LISTE
|
||||||
|
{
|
||||||
|
"type": "LISTE",
|
||||||
|
"metadata": {
|
||||||
|
"style": "bullet" // ou "numbered"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
##CODE
|
||||||
|
{
|
||||||
|
"type": "CODE",
|
||||||
|
"metadata": {
|
||||||
|
"language": "javascript", // ou "python", "html", etc.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
##PAGE
|
||||||
|
{
|
||||||
|
"type": "PAGE",
|
||||||
|
"metadata": {
|
||||||
|
"pageId": "123", // ID de la page cible
|
||||||
|
"title": "Nom de la page liée"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -159,18 +159,22 @@ public class Bloc extends ParamBD {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void updateBloc(int idBloc, String nouveauContenu) {
|
public static void updateBloc(int idBloc, String nouveauContenu, String type, String metadata) {
|
||||||
try {
|
try {
|
||||||
Connection connexion = DriverManager.getConnection(bdURL, bdLogin, bdPassword);
|
Connection connexion = DriverManager.getConnection(bdURL, bdLogin, bdPassword);
|
||||||
String sql = " UPDATE bloc"
|
String sql = " UPDATE bloc"
|
||||||
+ " SET contenu = ?"
|
+ " SET contenu = ?"
|
||||||
|
+ ", type = ?"
|
||||||
|
+ ", metadata = ?"
|
||||||
+ ", date_modification = ?"
|
+ ", date_modification = ?"
|
||||||
+ " WHERE id = ?"
|
+ " WHERE id = ?"
|
||||||
+ ";";
|
+ ";";
|
||||||
PreparedStatement pst = connexion.prepareStatement(sql);
|
PreparedStatement pst = connexion.prepareStatement(sql);
|
||||||
pst.setString(1, nouveauContenu);
|
pst.setString(1, nouveauContenu);
|
||||||
pst.setDate(2, Date.valueOf(LocalDate.now()));
|
pst.setString(2, type);
|
||||||
pst.setInt(3, idBloc);
|
pst.setString(3, metadata);
|
||||||
|
pst.setDate(4, Date.valueOf(LocalDate.now()));
|
||||||
|
pst.setInt(5, idBloc);
|
||||||
pst.executeUpdate();
|
pst.executeUpdate();
|
||||||
|
|
||||||
pst.close();
|
pst.close();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
package projet;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
import jakarta.websocket.*;
|
||||||
|
import jakarta.websocket.server.ServerEndpoint;
|
||||||
|
|
||||||
|
|
||||||
|
@ServerEndpoint("/ws/bloc")
|
||||||
|
public class BlocCollaborative {
|
||||||
|
|
||||||
|
private static final Map<String, Set<Session>> sessionsParPage = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@OnOpen
|
||||||
|
public void onOpen(Session session) {
|
||||||
|
String query = session.getQueryString();
|
||||||
|
String pageId = extractPageId(query);
|
||||||
|
session.getUserProperties().put("pageId", pageId);
|
||||||
|
|
||||||
|
sessionsParPage.computeIfAbsent(pageId, k -> ConcurrentHashMap.newKeySet()).add(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnMessage
|
||||||
|
public void onMessage(String message, Session session) {
|
||||||
|
String pageId = (String) session.getUserProperties().get("pageId");
|
||||||
|
Set<Session> sessions = sessionsParPage.get(pageId);
|
||||||
|
|
||||||
|
Lock sessionLock = new ReentrantLock();
|
||||||
|
|
||||||
|
synchronized (sessions) {
|
||||||
|
for (Session s : sessions) {
|
||||||
|
if (s.isOpen() && !s.equals(session)) {
|
||||||
|
sessionLock.lock();
|
||||||
|
try {
|
||||||
|
s.getAsyncRemote().sendText(message);
|
||||||
|
} finally {
|
||||||
|
sessionLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClose
|
||||||
|
public void onClose(Session session) {
|
||||||
|
String pageId = (String) session.getUserProperties().get("pageId");
|
||||||
|
if (pageId != null) {
|
||||||
|
Set<Session> sessions = sessionsParPage.get(pageId);
|
||||||
|
if (sessions != null) {
|
||||||
|
sessions.remove(session);
|
||||||
|
if (sessions.isEmpty()) {
|
||||||
|
sessionsParPage.remove(pageId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnError
|
||||||
|
public void onError(Session session, Throwable throwable) {
|
||||||
|
throwable.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractPageId(String query) {
|
||||||
|
for (String param : query.split("&")) {
|
||||||
|
String[] kv = param.split("=");
|
||||||
|
if (kv.length == 2 && kv[0].equals("pageId")) {
|
||||||
|
return kv[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "default";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -36,9 +36,12 @@ public class ModifBloc extends HttpServlet {
|
||||||
|
|
||||||
if(u != null) {
|
if(u != null) {
|
||||||
String contenu = request.getParameter("contenu");
|
String contenu = request.getParameter("contenu");
|
||||||
|
String type = request.getParameter("type");
|
||||||
|
String metadata = request.getParameter("metadata");
|
||||||
|
|
||||||
int blocId = Integer.parseInt(request.getParameter("blocId"));
|
int blocId = Integer.parseInt(request.getParameter("blocId"));
|
||||||
|
|
||||||
Bloc.updateBloc(blocId, contenu);
|
Bloc.updateBloc(blocId, contenu, type, metadata);
|
||||||
response.sendRedirect("AfficherPage");
|
response.sendRedirect("AfficherPage");
|
||||||
} else {
|
} else {
|
||||||
response.sendRedirect("/Projet/");
|
response.sendRedirect("/Projet/");
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
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.IOException;
|
||||||
|
|
||||||
|
@WebServlet("/Partage")
|
||||||
|
public class Partage extends HttpServlet {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public Partage() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
||||||
|
HttpSession session = request.getSession();
|
||||||
|
Utilisateur u = (Utilisateur) session.getAttribute("utilisateur");
|
||||||
|
|
||||||
|
String idPStr = request.getParameter("idP");
|
||||||
|
String idPageStr = request.getParameter("idPage");
|
||||||
|
|
||||||
|
if(u != null && idPStr != null) {
|
||||||
|
int idP = Integer.parseInt(idPStr);
|
||||||
|
int idPage = Integer.parseInt(idPageStr);
|
||||||
|
|
||||||
|
Page.partagerPage(idPage, u.getId(), idP);
|
||||||
|
response.sendRedirect("AfficherPage?id="+idPageStr);
|
||||||
|
}else {
|
||||||
|
response.sendRedirect("/Projet/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
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.IOException;
|
||||||
|
|
||||||
|
@WebServlet("/SupprimerMessage")
|
||||||
|
public class SupprimerMessage extends HttpServlet {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public SupprimerMessage() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
||||||
|
HttpSession session = request.getSession();
|
||||||
|
Utilisateur u = (Utilisateur) session.getAttribute("utilisateur");
|
||||||
|
|
||||||
|
if(u != null) {
|
||||||
|
if(u.getPrivilege().equals(Utilisateur.Privilege.ADMIN.name())) {
|
||||||
|
Message.effacerMessage();
|
||||||
|
}
|
||||||
|
response.sendRedirect("AfficherPage");
|
||||||
|
}else {
|
||||||
|
response.sendRedirect("/Projet/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
||||||
|
doGet(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,493 @@
|
||||||
|
|
||||||
|
|
||||||
|
// focus sur le dernier textarea quand la page s'ouvre
|
||||||
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const blocs = document.querySelectorAll('#md [contenteditable="true"]');
|
||||||
|
if (blocs.length > 0) {
|
||||||
|
blocs[blocs.length - 1].focus(); // focus sur le dernier bloc
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = document.querySelector(".messages-container");
|
||||||
|
if(container) container.scrollTop = container.scrollHeight; // Pour voir le dernier message du Tchat
|
||||||
|
|
||||||
|
|
||||||
|
// Sélectionne tous les dropdowns sur la page
|
||||||
|
const dropdowns = document.querySelectorAll('.dropdown');
|
||||||
|
|
||||||
|
dropdowns.forEach(function (dropdown) {
|
||||||
|
const trigger = dropdown.querySelector('.dropdown-trigger button');
|
||||||
|
|
||||||
|
trigger.addEventListener('click', function (event) {
|
||||||
|
event.stopPropagation(); // Évite que le clic remonte jusqu'au body
|
||||||
|
dropdown.classList.toggle('is-active');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fermer le menu si on clique en dehors
|
||||||
|
document.addEventListener('click', function () {
|
||||||
|
dropdowns.forEach(function (dropdown) {
|
||||||
|
dropdown.classList.remove('is-active');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ajouterBlocVideSiBesoin();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fonction pour ajouter un nouvel événement à chaque textarea
|
||||||
|
function addBlocEvent(bloc) {
|
||||||
|
|
||||||
|
bloc.addEventListener('keydown', function(event) {
|
||||||
|
|
||||||
|
if (event.key === 'Enter' && !event.shiftKey) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const currentBloc = event.target;
|
||||||
|
const texte = currentBloc.innerText.trim();
|
||||||
|
|
||||||
|
if (texte.startsWith("/")) {
|
||||||
|
// Gérer les commandes
|
||||||
|
handleSlashCommand(currentBloc, texte);
|
||||||
|
} else {
|
||||||
|
const blocId = currentBloc.getAttribute('data-id'); // Récupère l'ID du bloc
|
||||||
|
const type = currentBloc.getAttribute('data-type');
|
||||||
|
const metadata = currentBloc.getAttribute('metadata');
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
let contenuTexte = currentBloc.innerHTML
|
||||||
|
.replace(/<br\s*\/?>/gi, '\n') // Remplace les <br> par \n
|
||||||
|
.replace(/<b>/gi, '**') // Remplace <b> par **
|
||||||
|
.replace(/<\/b>/gi, '**') // Remplace </b> par **
|
||||||
|
.replace(/<i>/gi, '*') // Remplace <i> par *
|
||||||
|
.replace(/<\/i>/gi, '*'); // Remplace </i> par *
|
||||||
|
params.append("contenu", contenuTexte);
|
||||||
|
params.append("blocId", blocId);
|
||||||
|
params.append("metadata", metadata);
|
||||||
|
params.append("type", type);
|
||||||
|
|
||||||
|
fetch("/Projet/ModifBloc", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded"
|
||||||
|
},
|
||||||
|
body: params
|
||||||
|
}).then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error("Erreur lors de la mise à jour du bloc.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ajouterBlocVideSiBesoin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
bloc.addEventListener('input', function () {
|
||||||
|
autoResize(bloc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function autoResize(textarea) {
|
||||||
|
textarea.style.height = 'auto';
|
||||||
|
textarea.style.height = textarea.scrollHeight + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll('#md [contenteditable="true"]').forEach(bloc => {
|
||||||
|
addBlocEvent(bloc);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function ajouterBlocVideSiBesoin() {
|
||||||
|
const allBlocs = document.querySelectorAll('#md [contenteditable="true"]');
|
||||||
|
const pageId = new URLSearchParams(window.location.search).get("id");
|
||||||
|
if(pageId === null) return;
|
||||||
|
|
||||||
|
const message = document.querySelector('.column.is-half p');
|
||||||
|
if (message && message.textContent.trim() === "Pas encore de page choisie.") {
|
||||||
|
console.log("Aucune page choisie, fonction arrêtée.");
|
||||||
|
return; // Arrête la fonction ici (se passe après avoir supprimé une page)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allBlocs.length === 0 || allBlocs[allBlocs.length - 1].innerText.trim() !== "") {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("contenu", ""); // Bloc vide
|
||||||
|
params.append("type", "TEXTE"); // Type par défaut : TEXTE
|
||||||
|
params.append("ordre", allBlocs.length);
|
||||||
|
params.append("pageId", new URLSearchParams(window.location.search).get("id"));
|
||||||
|
|
||||||
|
fetch("/Projet/NouveauBloc", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded"
|
||||||
|
},
|
||||||
|
body: params.toString()
|
||||||
|
}).then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data && data.idGenere) {
|
||||||
|
const idGenere = data.idGenere;
|
||||||
|
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.classList.add('field', 'is-grouped', 'is-align-items-flex-start', 'bloc-container');
|
||||||
|
|
||||||
|
const control = document.createElement('div');
|
||||||
|
control.classList.add('control', 'is-expanded');
|
||||||
|
|
||||||
|
const newBloc = document.createElement('div');
|
||||||
|
newBloc.classList.add('textarea', 'is-primary', 'editor');
|
||||||
|
newBloc.setAttribute('contenteditable', 'true');
|
||||||
|
newBloc.setAttribute('rows', '1');
|
||||||
|
newBloc.setAttribute('data-type', 'TEXTE');
|
||||||
|
newBloc.setAttribute('data-id', idGenere);
|
||||||
|
newBloc.setAttribute('metadata', 'TEXTE');
|
||||||
|
|
||||||
|
control.appendChild(newBloc);
|
||||||
|
container.appendChild(control);
|
||||||
|
|
||||||
|
const delBtnWrapper = document.createElement('div');
|
||||||
|
delBtnWrapper.classList.add('control');
|
||||||
|
|
||||||
|
const delBtn = document.createElement('button');
|
||||||
|
delBtn.classList.add('delete', 'is-danger', 'delete-bloc-btn');
|
||||||
|
delBtn.setAttribute('data-id', idGenere);
|
||||||
|
|
||||||
|
delBtnWrapper.appendChild(delBtn);
|
||||||
|
container.appendChild(delBtnWrapper);
|
||||||
|
|
||||||
|
document.getElementById('md').appendChild(container);
|
||||||
|
|
||||||
|
addDeleteBloc(delBtn);
|
||||||
|
addBlocEvent(newBloc);
|
||||||
|
newBloc.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Vérifier si le dernier bloc est vide et lui appliquer le focus
|
||||||
|
const blocs = document.querySelectorAll('.editor'); // Sélectionne tous les blocs
|
||||||
|
if(blocs === null) return;
|
||||||
|
const lastBloc = blocs[blocs.length - 1]; // Dernier bloc
|
||||||
|
|
||||||
|
if (lastBloc && lastBloc.innerHTML.trim() === '') {
|
||||||
|
lastBloc.focus(); // Mettre le focus sur le dernier bloc si vide
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function addDeleteBloc(button) {
|
||||||
|
button.addEventListener('click', function () {
|
||||||
|
const blocId = button.dataset.id;
|
||||||
|
const blocContainer = button.closest('.bloc-container');
|
||||||
|
const bloc = blocContainer.querySelector('[contenteditable="true"]');
|
||||||
|
|
||||||
|
if (bloc.innerText.trim() === "") {
|
||||||
|
return; // Empêche la suppression si le textarea est vide
|
||||||
|
}
|
||||||
|
|
||||||
|
if (confirm("Voulez-vous vraiment supprimer ce bloc ?")) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("blocId", blocId);
|
||||||
|
|
||||||
|
fetch("/Projet/SupprimerBloc", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded"
|
||||||
|
},
|
||||||
|
body: params
|
||||||
|
}).then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
blocContainer.remove(); // Supprime visuellement le bloc
|
||||||
|
ajouterBlocVideSiBesoin();
|
||||||
|
} else {
|
||||||
|
console.error("Erreur lors de la suppression du bloc.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll('.delete-bloc-btn').forEach(t => {
|
||||||
|
addDeleteBloc(t);
|
||||||
|
});
|
||||||
|
|
||||||
|
function addDeletePage(button) {
|
||||||
|
button.addEventListener('click', function () {
|
||||||
|
const pageId = button.dataset.id;
|
||||||
|
|
||||||
|
if (confirm("Voulez-vous vraiment supprimer cette page ?")) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("pageId", pageId);
|
||||||
|
|
||||||
|
fetch("/Projet/SupprimerPage", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded"
|
||||||
|
},
|
||||||
|
body: params
|
||||||
|
}).then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
console.error("Erreur lors de la suppression du bloc.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll('.delete-page-btn').forEach(t => {
|
||||||
|
addDeletePage(t);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pour le Tchat
|
||||||
|
const socketTchat = new WebSocket("ws://" + window.location.host + "/Projet/ws/tchat");
|
||||||
|
|
||||||
|
socketTchat.onmessage = function(event) {
|
||||||
|
const container = document.querySelector(".messages-container");
|
||||||
|
const p = document.createElement("p");
|
||||||
|
p.textContent = event.data;
|
||||||
|
container.appendChild(p);
|
||||||
|
container.scrollTop = container.scrollHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
const input = document.querySelector("input[name='contenu']");
|
||||||
|
if (input) {
|
||||||
|
document.querySelector("input[name='contenu']").addEventListener("keydown", function(event) {
|
||||||
|
const login = document.getElementById("user-login").textContent;
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
const message = this.value;
|
||||||
|
if (message.trim() !== "") {
|
||||||
|
socketTchat.send(login + " : " + message);
|
||||||
|
this.value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Pour le travail collaboratif sur les blocs
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const id = params.get("id");
|
||||||
|
const pageId = parseInt(id);
|
||||||
|
const socketBloc = new WebSocket("ws://" + window.location.host + "/Projet/ws/bloc?pageId=" + pageId);
|
||||||
|
|
||||||
|
socketBloc.onmessage = (event) => {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
if (data.action === "update") {
|
||||||
|
const blocElement = document.querySelector(`.editor[data-id='${data.blocId}']`);
|
||||||
|
if (blocElement) {
|
||||||
|
blocElement.textContent = data.content;
|
||||||
|
blocElement.setAttribute('metadata', data.metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.querySelectorAll('.editor').forEach((bloc) => {
|
||||||
|
bloc.addEventListener('input', (event) => {
|
||||||
|
const blocId = event.target.getAttribute('data-id');
|
||||||
|
const content = event.target.textContent; // Récupère le contenu modifié du bloc
|
||||||
|
const metadata = event.target.getAttribute('metadata'); // Récupère le metadata du bloc
|
||||||
|
|
||||||
|
const modif = {
|
||||||
|
action: "update",
|
||||||
|
blocId: blocId,
|
||||||
|
content: content,
|
||||||
|
metadata: metadata
|
||||||
|
};
|
||||||
|
|
||||||
|
socketBloc.send(JSON.stringify(modif)); // Envoi de la modification via le WebSocket
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.editor').forEach(function(element) {
|
||||||
|
let content = element.innerHTML;
|
||||||
|
content = content.replace(/\n/g, '<br />');
|
||||||
|
content = content.replace(/\*\*(.*?)\*\*/g, '<b>$1</b>');
|
||||||
|
content = content.replace(/\*(.*?)\*/g, '<i>$1</i>');
|
||||||
|
element.innerHTML = content;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// pour modifier le rendu des blocs
|
||||||
|
function getBlocMetadata(bloc) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(bloc.dataset.metadata || "{}");
|
||||||
|
} catch (e) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setBlocMetadata(bloc, newMetadata) {
|
||||||
|
bloc.dataset.metadata = JSON.stringify(newMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyBlocType(bloc, type, extra = {}) {
|
||||||
|
bloc.dataset.type = type;
|
||||||
|
|
||||||
|
let metadata = {};
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case "TEXTE":
|
||||||
|
metadata = {};
|
||||||
|
break;
|
||||||
|
case "TITRE":
|
||||||
|
metadata = { level: extra.level || 1 };
|
||||||
|
break;
|
||||||
|
case "LISTE":
|
||||||
|
metadata = { style: extra.style || "bullet" };
|
||||||
|
break;
|
||||||
|
case "CODE":
|
||||||
|
metadata = { language: extra.language || "plaintext", theme: "light" };
|
||||||
|
break;
|
||||||
|
case "PAGE":
|
||||||
|
metadata = {
|
||||||
|
pageId: extra.pageId || "nouvelle-page-id",
|
||||||
|
title: extra.title || "Nouvelle page"
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warn("Type de bloc inconnu :", type);
|
||||||
|
}
|
||||||
|
|
||||||
|
setBlocMetadata(bloc, metadata);
|
||||||
|
renderBlocStyle(bloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderBlocStyle(bloc) {
|
||||||
|
if (!bloc || !bloc.dataset) return;
|
||||||
|
|
||||||
|
// Nettoyage des anciennes classes
|
||||||
|
bloc.classList.remove('is-title', 'is-title-1', 'is-title-2', 'is-title-3');
|
||||||
|
bloc.classList.remove('is-code-block', 'is-list', 'is-toggle');
|
||||||
|
bloc.setAttribute('placeholder', 'Tapez ici...');
|
||||||
|
bloc.style.whiteSpace = "normal"; // reset si code
|
||||||
|
|
||||||
|
const type = bloc.dataset.type || 'TEXTE';
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'TEXTE':
|
||||||
|
bloc.classList.add('is-text');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'TITRE':
|
||||||
|
const level = bloc.dataset.level || '2';
|
||||||
|
bloc.classList.add('is-title', `is-title-${level}`);
|
||||||
|
bloc.setAttribute('placeholder', `Titre niveau ${level}`);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'LISTE':
|
||||||
|
bloc.classList.add('is-list');
|
||||||
|
bloc.setAttribute('placeholder', '• Élément de liste');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'CODE':
|
||||||
|
bloc.classList.add('is-code-block');
|
||||||
|
bloc.style.whiteSpace = "pre";
|
||||||
|
const lang = bloc.dataset.language || 'plaintext';
|
||||||
|
bloc.setAttribute('placeholder', `Code (${lang})`);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'PAGE':
|
||||||
|
bloc.classList.add('is-page-block');
|
||||||
|
bloc.setAttribute('placeholder', 'Nouvelle page...');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'TOGGLE':
|
||||||
|
bloc.classList.add('is-toggle');
|
||||||
|
bloc.setAttribute('placeholder', 'Cliquez pour développer...');
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
bloc.classList.add('is-text');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function handleSlashCommand(bloc, texte) {
|
||||||
|
const parts = texte.substring(1).split(" "); // Supprimer le "/" et diviser
|
||||||
|
const command = parts[0].toLowerCase();
|
||||||
|
const param = parts.slice(1).join(" "); // Récupère les paramètres (par exemple "js" pour /code js)
|
||||||
|
|
||||||
|
switch (command) {
|
||||||
|
case "page":
|
||||||
|
createNewPage(param || "Nouvelle page");
|
||||||
|
bloc.textContent = param || "Nouvelle page"; // On vide le bloc après transformation
|
||||||
|
autoResize(bloc);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "h1":
|
||||||
|
case "h2":
|
||||||
|
case "h3":
|
||||||
|
applyBlocType(bloc, "TITRE", { level: parseInt(command[1] || "1") });
|
||||||
|
bloc.textContent = ''; // On vide le bloc après transformation
|
||||||
|
autoResize(bloc);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "ul":
|
||||||
|
applyBlocType(bloc, "LISTE", { style: "bullet" });
|
||||||
|
bloc.textContent = ''; // On vide le bloc après transformation
|
||||||
|
autoResize(bloc);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "ol":
|
||||||
|
applyBlocType(bloc, "LISTE", { style: "numbered" });
|
||||||
|
bloc.textContent = ''; // On vide le bloc après transformation
|
||||||
|
autoResize(bloc);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "code":
|
||||||
|
applyBlocType(bloc, "CODE", { language: param || "plaintext" });
|
||||||
|
bloc.textContent = ''; // On vide le bloc après transformation
|
||||||
|
autoResize(bloc);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.log(`Commande non reconnue: ${command}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bloc.focus();
|
||||||
|
placeCursorAtEnd(bloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
function placeCursorAtEnd(element) {
|
||||||
|
const range = document.createRange();
|
||||||
|
const selection = window.getSelection();
|
||||||
|
|
||||||
|
range.selectNodeContents(element);
|
||||||
|
range.collapse(false);
|
||||||
|
|
||||||
|
selection.removeAllRanges();
|
||||||
|
selection.addRange(range);
|
||||||
|
|
||||||
|
element.scrollIntoView({ behavior: "smooth", block: "end" });
|
||||||
|
}
|
||||||
|
|
||||||
|
function createNewPage(titre) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("titre", titre);
|
||||||
|
|
||||||
|
fetch("/Projet/NouvellePage", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded"
|
||||||
|
},
|
||||||
|
body: params.toString()
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data && data.pageId) {
|
||||||
|
// Rediriger vers la nouvelle page par exemple
|
||||||
|
window.location.href = "/Projet/Page?id=" + data.pageId;
|
||||||
|
} else {
|
||||||
|
console.error("Erreur lors de la création de la page.");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error("Erreur réseau :", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
@charset "UTF-8";
|
||||||
|
|
||||||
|
|
||||||
|
.editor {
|
||||||
|
resize: none; /* Empêche le redimensionnement par l'utilisateur */
|
||||||
|
overflow-y: hidden; /* Masquer la barre de défilement verticale */
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.tchat-container {
|
||||||
|
display: block;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
.messages-container {
|
||||||
|
overflow-y: auto;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #f9f9f9;
|
||||||
|
}
|
||||||
|
.input-container {
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* pour les blocs */
|
||||||
|
.is-title-1 { font-size: 2em; font-weight: bold; }
|
||||||
|
.is-title-2 { font-size: 1.5em; font-weight: bold; }
|
||||||
|
.is-title-3 { font-size: 1.2em; font-weight: bold; }
|
||||||
|
|
||||||
|
.is-code-block {
|
||||||
|
font-family: monospace;
|
||||||
|
background: #f6f6f6;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-list::before {
|
||||||
|
content: "• ";
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-toggle::before {
|
||||||
|
content: "▶ ";
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue