Markdown on !!
This commit is contained in:
parent
406e69e5f4
commit
01d08ab44a
|
|
@ -43,7 +43,21 @@
|
||||||
"type": "PAGE",
|
"type": "PAGE",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"pageId": "123", // ID de la page cible
|
"pageId": "123", // ID de la page cible
|
||||||
"title": "Nom de la page liée"
|
"title": "Nom de la page liée",
|
||||||
|
"from" : "125" // ID de la page
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
##SEPARATEUR
|
||||||
|
{
|
||||||
|
"type": "SEPARATEUR",
|
||||||
|
"metadata": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
##CITATION
|
||||||
|
{
|
||||||
|
"type": "CITATION",
|
||||||
|
"metadata": {
|
||||||
|
"type": 'danger', 'info' ou 'normal'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
CREATE DATABASE IF NOT EXISTS projet;
|
||||||
|
USE projet;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS bloc;
|
DROP TABLE IF EXISTS bloc;
|
||||||
DROP TABLE IF EXISTS partage;
|
DROP TABLE IF EXISTS partage;
|
||||||
DROP TABLE IF EXISTS page;
|
DROP TABLE IF EXISTS page;
|
||||||
|
|
@ -18,19 +21,21 @@ CREATE TABLE page (
|
||||||
date_modification DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
date_modification DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
auteur_id INT NOT NULL,
|
auteur_id INT NOT NULL,
|
||||||
droits ENUM('LECTURE', 'ECRITURE', 'ADMIN') NOT NULL,
|
droits ENUM('LECTURE', 'ECRITURE', 'ADMIN') NOT NULL,
|
||||||
FOREIGN KEY (auteur_id) REFERENCES utilisateur(id) ON DELETE CASCADE
|
page_parent_id INT DEFAULT NULL,
|
||||||
|
FOREIGN KEY (auteur_id) REFERENCES utilisateur(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (page_parent_id) REFERENCES page(id) ON DELETE SET NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE bloc (
|
CREATE TABLE bloc (
|
||||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||||
type ENUM('TEXTE', 'LISTE', 'TITRE', 'CODE', 'PAGE') NOT NULL DEFAULT 'TEXTE',
|
type ENUM('TEXTE', 'LISTE', 'TITRE', 'CODE', 'PAGE', 'SEPARATEUR', 'CITATION') NOT NULL DEFAULT 'TEXTE',
|
||||||
contenu TEXT,
|
contenu TEXT,
|
||||||
date_creation DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
date_creation DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
date_modification DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
date_modification DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
page_id INT NOT NULL,
|
page_id INT NOT NULL,
|
||||||
ordre INT NOT NULL,
|
ordre INT NOT NULL,
|
||||||
auteur_id INT NOT NULL,
|
auteur_id INT NOT NULL,
|
||||||
metadata JSON NOT NULL DEFAULT '{}',
|
metadata JSON NOT NULL,
|
||||||
FOREIGN KEY (page_id) REFERENCES page(id) ON DELETE CASCADE,
|
FOREIGN KEY (page_id) REFERENCES page(id) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (auteur_id) REFERENCES utilisateur(id) ON DELETE CASCADE
|
FOREIGN KEY (auteur_id) REFERENCES utilisateur(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,9 @@ public class Bloc extends ParamBD {
|
||||||
LISTE,
|
LISTE,
|
||||||
TITRE,
|
TITRE,
|
||||||
CODE,
|
CODE,
|
||||||
PAGE
|
PAGE,
|
||||||
|
SEPARATEUR,
|
||||||
|
CITATION
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bloc() {
|
public Bloc() {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package projet;
|
||||||
|
|
||||||
|
public interface BlocRenderer {
|
||||||
|
|
||||||
|
String renderTexte(String contenu);
|
||||||
|
String renderListe(String contenu);
|
||||||
|
String renderTitre(String contenu);
|
||||||
|
String renderCode(String contenu);
|
||||||
|
String renderPage(String contenu);
|
||||||
|
String renderSeparateur();
|
||||||
|
String renderCitation(String contenu);
|
||||||
|
|
||||||
|
default String render(Bloc bloc) {
|
||||||
|
switch (bloc.getType()) {
|
||||||
|
case TEXTE:
|
||||||
|
return renderTexte(bloc.getContenu());
|
||||||
|
case LISTE:
|
||||||
|
return renderListe(bloc.getContenu());
|
||||||
|
case TITRE:
|
||||||
|
return renderTitre(bloc.getContenu());
|
||||||
|
case CODE:
|
||||||
|
return renderCode(bloc.getContenu());
|
||||||
|
case PAGE:
|
||||||
|
return renderPage(bloc.getContenu());
|
||||||
|
case SEPARATEUR:
|
||||||
|
return renderSeparateur();
|
||||||
|
case CITATION:
|
||||||
|
return renderCitation(bloc.getContenu());
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Type de bloc inconnu : " + bloc.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
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;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
@WebServlet("/NouvellePageDirect")
|
||||||
|
public class NouvellePageDirect extends HttpServlet {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public NouvellePageDirect() {
|
||||||
|
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 {
|
||||||
|
HttpSession session = request.getSession();
|
||||||
|
Utilisateur u = (Utilisateur) session.getAttribute("utilisateur");
|
||||||
|
|
||||||
|
response.setContentType("application/json");
|
||||||
|
response.setCharacterEncoding("UTF-8");
|
||||||
|
|
||||||
|
if(u != null) {
|
||||||
|
request.setCharacterEncoding("UTF-8");
|
||||||
|
|
||||||
|
String titre = request.getParameter("titre");
|
||||||
|
String pageIdStr = request.getParameter("pageId");
|
||||||
|
|
||||||
|
if (titre == null || titre.trim().isEmpty()) {
|
||||||
|
response.getWriter().write("{\"error\": \"Titre vide\"}");
|
||||||
|
} else {
|
||||||
|
int pageId = Integer.parseInt(pageIdStr);
|
||||||
|
int id = Page.ajouterPage(u.getId(), titre, LocalDate.now(), pageId);
|
||||||
|
if (id != -1) {
|
||||||
|
response.getWriter().write("{\"id\": " + id + "," + "\"pageId\": " + pageId + "}");
|
||||||
|
} else {
|
||||||
|
response.getWriter().write("{\"error\": \"Erreur de création\"}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
response.getWriter().write("{\"error\": \"Utilisateur non connecté\"}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -144,6 +144,42 @@ public class Page extends ParamBD {
|
||||||
return idGenere;
|
return idGenere;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static int ajouterPage(int idU, String t, LocalDate dl, int idParent) {
|
||||||
|
int idGenere = -1;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Connection connexion = DriverManager.getConnection(bdURL, bdLogin, bdPassword);
|
||||||
|
String sql = " INSERT INTO page(titre, date_creation, date_modification, auteur_id, droits, page_parent_id)"
|
||||||
|
+ " VALUES (?, ?, ?, ?, ?, ?)"
|
||||||
|
+ ";";
|
||||||
|
PreparedStatement pst = connexion.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
|
||||||
|
pst.setString(1, t);
|
||||||
|
pst.setDate(2, Date.valueOf(dl));
|
||||||
|
pst.setDate(3, Date.valueOf(dl));
|
||||||
|
pst.setInt(4, idU);
|
||||||
|
pst.setString(5, "ADMIN");
|
||||||
|
pst.setInt(6, idParent);
|
||||||
|
pst.executeUpdate();
|
||||||
|
|
||||||
|
ResultSet rs = pst.getGeneratedKeys();
|
||||||
|
if (rs.next()) {
|
||||||
|
idGenere = rs.getInt(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
rs.close();
|
||||||
|
pst.close();
|
||||||
|
connexion.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idGenere != -1) {
|
||||||
|
Bloc.ajouterBlocVide(idU, idGenere);
|
||||||
|
}
|
||||||
|
|
||||||
|
return idGenere;
|
||||||
|
}
|
||||||
|
|
||||||
protected static Page getPageById(int idU, int id) {
|
protected static Page getPageById(int idU, int id) {
|
||||||
Page page = new Page();
|
Page page = new Page();
|
||||||
String titre = null;
|
String titre = null;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
</jsp:include>
|
</jsp:include>
|
||||||
|
|
||||||
|
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
|
|
||||||
<!-- 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">
|
||||||
|
|
@ -42,16 +42,15 @@
|
||||||
</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">
|
<div class="field is-grouped is-align-items-flex-start bloc-container" draggable="true">
|
||||||
<div class="control is-expanded">
|
<div class="control is-expanded">
|
||||||
<div
|
<div
|
||||||
class="textarea is-primary editor"
|
class="is-primary editor"
|
||||||
contenteditable="true"
|
contenteditable="true"
|
||||||
rows="1"
|
|
||||||
data-id="${bloc.id}"
|
data-id="${bloc.id}"
|
||||||
data-ordre="${bloc.ordre}"
|
data-ordre="${bloc.ordre}"
|
||||||
data-type="${bloc.type}"
|
data-type="${bloc.type}"
|
||||||
metadata="${bloc.metadata}"
|
data-metadata='${bloc.metadata}'
|
||||||
>${bloc.contenu}</div>
|
>${bloc.contenu}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
|
|
@ -71,7 +70,7 @@
|
||||||
<div class="column is-one-quarter">
|
<div class="column is-one-quarter">
|
||||||
<jsp:include page="Tchat.jsp" />
|
<jsp:include page="Tchat.jsp" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<jsp:include page="Footer.jsp" />
|
<jsp:include page="Footer.jsp" />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,6 @@
|
||||||
</a>
|
</a>
|
||||||
</footer>
|
</footer>
|
||||||
</main>
|
</main>
|
||||||
<script src="script.js?v=<%= System.currentTimeMillis() %>"></script>
|
<script type="module" src="js/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -8,6 +8,9 @@
|
||||||
<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
|
||||||
|
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">
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,9 @@
|
||||||
|
|
||||||
</li>
|
</li>
|
||||||
</c:forEach>
|
</c:forEach>
|
||||||
|
</ul>
|
||||||
<hr>
|
<hr>
|
||||||
|
<ul class="menu-list">
|
||||||
<c:forEach var="pagePartagees" items="${listePagesPartagees}">
|
<c:forEach var="pagePartagees" items="${listePagesPartagees}">
|
||||||
<li>
|
<li>
|
||||||
<i class="fa-solid fa-share-nodes is-pulled-right"></i>
|
<i class="fa-solid fa-share-nodes is-pulled-right"></i>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
pageEncoding="UTF-8"%>
|
pageEncoding="UTF-8"%>
|
||||||
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
|
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
|
||||||
|
|
||||||
<div class="tchat-container">
|
<div class="tchat-container">
|
||||||
<h2 class="block">Tchat</h2>
|
<h2 class="block">Tchat</h2>
|
||||||
<div class="messages-container" id="Messages" style="height: 60vh;">
|
<div class="messages-container" id="Messages" style="height: 60vh;">
|
||||||
<c:forEach var="message" items="${listeMessages}">
|
<c:forEach var="message" items="${listeMessages}">
|
||||||
|
|
@ -19,4 +19,4 @@
|
||||||
placeholder="Tchatez ici"
|
placeholder="Tchatez ici"
|
||||||
name="contenu">
|
name="contenu">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -0,0 +1,433 @@
|
||||||
|
import { autoResize, placeCursorAtEnd } from './utils.js';
|
||||||
|
import { createNewPage } from './page.js';
|
||||||
|
|
||||||
|
export function initBlocs() {
|
||||||
|
document.querySelectorAll('#md [contenteditable="true"]').forEach(bloc => {
|
||||||
|
addBlocEvent(bloc);
|
||||||
|
renderBlocStyle(bloc);
|
||||||
|
autoResize(bloc);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.editor a').forEach(link => {
|
||||||
|
link.addEventListener('click', event => {
|
||||||
|
event.preventDefault();
|
||||||
|
window.location.href = link.href;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.delete-bloc-btn').forEach(t => {
|
||||||
|
addDeleteBloc(t);
|
||||||
|
});
|
||||||
|
|
||||||
|
formatEditorContent();
|
||||||
|
|
||||||
|
document.querySelectorAll('#md [contenteditable="true"]').forEach(bloc => {
|
||||||
|
autoResize(bloc);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatEditorContent() {
|
||||||
|
document.querySelectorAll('.editor').forEach(function(element) {
|
||||||
|
let content = element.innerHTML;
|
||||||
|
|
||||||
|
content = content.replace(/\n/g, '<br />');
|
||||||
|
content = content.replace(/`(.*?)`/g, '<code>$1</code> ');
|
||||||
|
content = content.replace(/\*\*(.*?)\*\*/g, '<b>$1</b> ');
|
||||||
|
content = content.replace(/\*(.*?)\*/g, '<i>$1</i> ');
|
||||||
|
element.innerHTML = content;
|
||||||
|
|
||||||
|
// Appliquer le rendu MathJax après modification du contenu
|
||||||
|
MathJax.typeset();
|
||||||
|
autoResize(element);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addBlocEvent(bloc) {
|
||||||
|
bloc.addEventListener('keydown', async event => {
|
||||||
|
if (event.key === 'Enter' && !event.shiftKey) {
|
||||||
|
event.preventDefault();
|
||||||
|
const texte = bloc.innerText.trim();
|
||||||
|
if (texte.startsWith("/")) {
|
||||||
|
// Gérer les instructions commençant par /
|
||||||
|
await handleSlashCommand(bloc, texte);
|
||||||
|
} else {
|
||||||
|
// Gérer les titres et citations markdown
|
||||||
|
await handleMarkdownSyntax(bloc, texte);
|
||||||
|
}
|
||||||
|
sauvegarderBloc(bloc);
|
||||||
|
ajouterBlocVideSiBesoin();
|
||||||
|
formatEditorContent();
|
||||||
|
focusDernierBloc();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
bloc.addEventListener('input', () => autoResize(bloc));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sauvegarderBloc(bloc) {
|
||||||
|
const blocId = bloc.getAttribute('data-id');
|
||||||
|
const type = bloc.getAttribute('data-type');
|
||||||
|
const metadata = bloc.getAttribute('data-metadata');
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
let contenuTexte = bloc.innerHTML
|
||||||
|
.replace(/<br\s*\/?>/gi, '\n')
|
||||||
|
.replace(/<b>/gi, '**')
|
||||||
|
.replace(/<\/b>/gi, '**')
|
||||||
|
.replace(/<i>/gi, '*')
|
||||||
|
.replace(/<\/i>/gi, '*')
|
||||||
|
.replace(/<code>/gi, '`')
|
||||||
|
.replace(/<\/code>/gi, '`');
|
||||||
|
|
||||||
|
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
|
||||||
|
}).catch(() => console.error("Erreur lors de la mise à jour du bloc."));
|
||||||
|
}
|
||||||
|
|
||||||
|
export 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.dataset.type != "SEPARATEUR" && bloc.innerText === "") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export 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.") {
|
||||||
|
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("metadata", "{}");
|
||||||
|
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('is-primary', 'editor');
|
||||||
|
newBloc.setAttribute('contenteditable', 'true');
|
||||||
|
newBloc.setAttribute('data-type', 'TEXTE');
|
||||||
|
newBloc.setAttribute('data-id', idGenere);
|
||||||
|
newBloc.setAttribute('data-metadata', '{}');
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function focusDernierBloc() {
|
||||||
|
const blocs = document.querySelectorAll('.editor'); // Remplace `.bloc` par ta classe de bloc
|
||||||
|
const dernierBloc = blocs[blocs.length - 1];
|
||||||
|
if (dernierBloc) {
|
||||||
|
dernierBloc.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSlashCommand(bloc, texte) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const parts = texte.trim().substring(1).split(" ");
|
||||||
|
const command = parts[0].toLowerCase();
|
||||||
|
const param = parts.slice(1).join(" ");
|
||||||
|
|
||||||
|
switch (command) {
|
||||||
|
case "page":
|
||||||
|
const pageId = new URLSearchParams(window.location.search).get("id");
|
||||||
|
createNewPage(param || "Nouvelle page", pageId).then(data => {
|
||||||
|
if (data && data.id) {
|
||||||
|
const lien = `/Projet/AfficherPage?id=${data.id}`;
|
||||||
|
bloc.innerHTML = `<a href="${lien}">${param || "Nouvelle page"}</a>`;
|
||||||
|
applyBlocType(bloc, "PAGE", { pageId: data.id, title: param, from: pageId });
|
||||||
|
autoResize(bloc);
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
console.error("Erreur de création de page");
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
console.error("Erreur lors de la création de la page :", error);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "h1":
|
||||||
|
case "h2":
|
||||||
|
case "h3":
|
||||||
|
applyBlocType(bloc, "TITRE", { level: parseInt(command[1] || "1") });
|
||||||
|
bloc.textContent = param;
|
||||||
|
autoResize(bloc);
|
||||||
|
resolve();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "ul":
|
||||||
|
applyBlocType(bloc, "LISTE", { style: "bullet" });
|
||||||
|
bloc.textContent = '';
|
||||||
|
autoResize(bloc);
|
||||||
|
resolve();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "ol":
|
||||||
|
applyBlocType(bloc, "LISTE", { style: "numbered" });
|
||||||
|
bloc.textContent = '';
|
||||||
|
autoResize(bloc);
|
||||||
|
resolve();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "code":
|
||||||
|
applyBlocType(bloc, "CODE", { language: param || "plaintext" });
|
||||||
|
bloc.textContent = '';
|
||||||
|
autoResize(bloc);
|
||||||
|
resolve();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "hr":
|
||||||
|
applyBlocType(bloc, "SEPARATEUR", {}); // ou un type adapté selon ta logique
|
||||||
|
bloc.innerText = ' ';
|
||||||
|
autoResize(bloc);
|
||||||
|
resolve(true);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.log(`Commande non reconnue: ${command}`);
|
||||||
|
resolve();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bloc.focus();
|
||||||
|
placeCursorAtEnd(bloc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const citationTypes = {
|
||||||
|
'>!': 'danger',
|
||||||
|
'>i': 'info',
|
||||||
|
/* '>w': 'warning',
|
||||||
|
'>s': 'success',*/
|
||||||
|
'>': 'normal' // doit rester en dernier
|
||||||
|
};
|
||||||
|
|
||||||
|
function detectCitation(texte) {
|
||||||
|
for (const prefix in citationTypes) {
|
||||||
|
if (texte.startsWith(prefix)) {
|
||||||
|
return {
|
||||||
|
type: citationTypes[prefix],
|
||||||
|
content: texte.replace(new RegExp(`^${prefix}\\s*`), '')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
if (level >= 1 && level <= 6) {
|
||||||
|
applyBlocType(bloc, "TITRE", { level: level });
|
||||||
|
bloc.textContent = texte.replace(/^#+\s*/, '');
|
||||||
|
autoResize(bloc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Vérifier si c'est une citation
|
||||||
|
else if (texte.startsWith('>')) {
|
||||||
|
const citation = detectCitation(texte);
|
||||||
|
applyBlocType(bloc, "CITATION", { type: citation.type });
|
||||||
|
bloc.textContent = citation.content;
|
||||||
|
autoResize(bloc);
|
||||||
|
}
|
||||||
|
// Vérifier si c'est un bloc de code
|
||||||
|
else if (texte.startsWith('```')) {
|
||||||
|
applyBlocType(bloc, "CODE", {});
|
||||||
|
bloc.textContent = texte.replace(/^```(.*)$/, '');
|
||||||
|
autoResize(bloc);
|
||||||
|
}
|
||||||
|
// Vérifier si c'est un séparateur horizontal (Markdown)
|
||||||
|
else if (texte.match(/^(\*{3,}|\-{3,}|_{3,})$/)) {
|
||||||
|
applyBlocType(bloc, "SEPARATEUR", {});
|
||||||
|
bloc.textContent = '';
|
||||||
|
autoResize(bloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBlocDataMetadata(bloc) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(bloc.getAttribute('data-metadata') || "{}");
|
||||||
|
} catch (e) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setBlocMetadata(bloc, metadata) {
|
||||||
|
const metadataStr = JSON.stringify(metadata);
|
||||||
|
bloc.dataset.metadata = metadataStr;
|
||||||
|
bloc.setAttribute('data-metadata', metadataStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export 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-quote', 'is-quote-info', 'is-quote-warning', 'is-quote-normal');
|
||||||
|
bloc.classList.remove('is-code-block', 'is-list', 'is-page-link', 'is-separator');
|
||||||
|
bloc.style.whiteSpace = "normal"; // reset eventuel
|
||||||
|
|
||||||
|
const type = bloc.dataset.type || 'TEXTE';
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'TEXTE':
|
||||||
|
bloc.classList.add('is-text');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'TITRE':
|
||||||
|
const metadata = getBlocDataMetadata(bloc);
|
||||||
|
const level = metadata.level || '1';
|
||||||
|
bloc.classList.add('is-title', `is-title-${level}`);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'LISTE':
|
||||||
|
bloc.classList.add('is-list');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'CODE':
|
||||||
|
bloc.classList.add('is-code-block');
|
||||||
|
bloc.style.whiteSpace = "pre";
|
||||||
|
bloc.dataset.language || 'plaintext';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'PAGE':
|
||||||
|
bloc.classList.add('is-page-link');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'SEPARATEUR':
|
||||||
|
bloc.classList.add('is-separator');
|
||||||
|
bloc.contentEditable = "false";
|
||||||
|
break;
|
||||||
|
case "CITATION":
|
||||||
|
const metadataCitation = getBlocDataMetadata(bloc);
|
||||||
|
const citationType = metadataCitation.type || 'normal';
|
||||||
|
bloc.classList.add('is-quote', `is-quote-${citationType}`);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
bloc.classList.add('is-text');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
autoResize(bloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
case "SEPARATEUR":
|
||||||
|
metadata = {};
|
||||||
|
break;
|
||||||
|
case "CITATION":
|
||||||
|
metadata = {
|
||||||
|
type: extra.type || "normal"
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warn("Type de bloc inconnu :", type);
|
||||||
|
}
|
||||||
|
|
||||||
|
setBlocMetadata(bloc, metadata);
|
||||||
|
renderBlocStyle(bloc);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
// dragDropModule.js
|
||||||
|
|
||||||
|
// Initialisation de l'élément drag et drop
|
||||||
|
let draggedBloc = null;
|
||||||
|
let dropIndicator = null;
|
||||||
|
|
||||||
|
// Ajout des événements pour les blocs
|
||||||
|
export function initializeDragDrop() {
|
||||||
|
document.querySelectorAll('.bloc-container').forEach(bloc => {
|
||||||
|
bloc.addEventListener('dragstart', function (e) {
|
||||||
|
draggedBloc = bloc;
|
||||||
|
bloc.classList.add('dragging');
|
||||||
|
|
||||||
|
// Créer une copie du bloc qui suivra la souris
|
||||||
|
const clone = bloc.cloneNode(true);
|
||||||
|
clone.classList.add('dragging-clone');
|
||||||
|
document.body.appendChild(clone);
|
||||||
|
draggedBloc.style.visibility = 'hidden'; // Rendre le bloc original invisible
|
||||||
|
|
||||||
|
// Mise à jour de la position du clone pendant le déplacement
|
||||||
|
document.addEventListener('mousemove', handleMouseMove);
|
||||||
|
});
|
||||||
|
|
||||||
|
bloc.addEventListener('dragend', function () {
|
||||||
|
bloc.classList.remove('dragging');
|
||||||
|
if (dropIndicator) {
|
||||||
|
dropIndicator.remove();
|
||||||
|
dropIndicator = null;
|
||||||
|
}
|
||||||
|
if (draggedBloc) {
|
||||||
|
draggedBloc.style.visibility = ''; // Rendre le bloc original visible à la fin
|
||||||
|
}
|
||||||
|
document.removeEventListener('mousemove', handleMouseMove);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Gérer l'événement de dragover sur le document
|
||||||
|
document.addEventListener('dragover', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const blocContainers = Array.from(document.querySelectorAll('.bloc-container'));
|
||||||
|
const afterElement = getDragAfterElement(blocContainers, e.clientY);
|
||||||
|
|
||||||
|
if (afterElement) {
|
||||||
|
afterElement.parentNode.insertBefore(dropIndicator, afterElement);
|
||||||
|
} else {
|
||||||
|
const lastBlocContainer = document.querySelector('.bloc-container:last-child');
|
||||||
|
if (lastBlocContainer) {
|
||||||
|
lastBlocContainer.after(dropIndicator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Gérer l'événement de drop sur le document
|
||||||
|
document.addEventListener('drop', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!dropIndicator || !draggedBloc) return;
|
||||||
|
|
||||||
|
if (dropIndicator.parentNode) {
|
||||||
|
dropIndicator.parentNode.insertBefore(draggedBloc, dropIndicator);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sauvegarder la nouvelle position des blocs
|
||||||
|
saveBlocOrder();
|
||||||
|
|
||||||
|
dropIndicator.remove();
|
||||||
|
dropIndicator = null;
|
||||||
|
draggedBloc = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mise à jour de la position du clone pendant le déplacement de la souris
|
||||||
|
function handleMouseMove(e) {
|
||||||
|
const draggedClone = document.querySelector('.dragging-clone');
|
||||||
|
if (draggedClone) {
|
||||||
|
draggedClone.style.position = 'absolute';
|
||||||
|
draggedClone.style.left = `${e.pageX}px`;
|
||||||
|
draggedClone.style.top = `${e.pageY}px`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trouver où insérer l'élément pendant le drag
|
||||||
|
function getDragAfterElement(containers, mouseY) {
|
||||||
|
return containers.reduce((closest, child) => {
|
||||||
|
const box = child.getBoundingClientRect();
|
||||||
|
const offset = mouseY - box.top - box.height / 2;
|
||||||
|
if (offset < 0 && offset > closest.offset) {
|
||||||
|
return { offset: offset, element: child };
|
||||||
|
} else {
|
||||||
|
return closest;
|
||||||
|
}
|
||||||
|
}, { offset: Number.NEGATIVE_INFINITY }).element;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction pour sauvegarder l'ordre des blocs dans la base de données
|
||||||
|
function saveBlocOrder() {
|
||||||
|
const blocIds = Array.from(document.querySelectorAll('.bloc-container')).map(bloc => bloc.dataset.id);
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('order', blocIds.join(','));
|
||||||
|
|
||||||
|
fetch('/Projet/UpdateOrder', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
},
|
||||||
|
body: params
|
||||||
|
}).then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error('Erreur lors de la sauvegarde de l’ordre');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
export function initDropdowns() {
|
||||||
|
const dropdowns = document.querySelectorAll('.dropdown');
|
||||||
|
|
||||||
|
dropdowns.forEach(dropdown => {
|
||||||
|
const trigger = dropdown.querySelector('.dropdown-trigger button');
|
||||||
|
|
||||||
|
trigger.addEventListener('click', event => {
|
||||||
|
event.stopPropagation();
|
||||||
|
dropdown.classList.toggle('is-active');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('click', () => {
|
||||||
|
dropdowns.forEach(dropdown => dropdown.classList.remove('is-active'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { initBlocs, ajouterBlocVideSiBesoin, focusDernierBloc } from './bloc.js';
|
||||||
|
import { initTchat } from './tchat.js';
|
||||||
|
import { initPages } from './page.js';
|
||||||
|
import { initDropdowns } from './dropdown.js';
|
||||||
|
import { initSocketBloc } from './socket-bloc.js';
|
||||||
|
import { initializeDragDrop } from './drag-and-drop.js';
|
||||||
|
|
||||||
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
|
initBlocs();
|
||||||
|
initDropdowns();
|
||||||
|
initTchat();
|
||||||
|
initPages();
|
||||||
|
initSocketBloc();
|
||||||
|
ajouterBlocVideSiBesoin();
|
||||||
|
focusDernierBloc();
|
||||||
|
|
||||||
|
document.body.addEventListener('click', function(event) {
|
||||||
|
// Vérifie si l'élément cliqué est un lien dans un .editor
|
||||||
|
if (event.target.tagName.toLowerCase() === 'a' && event.target.closest('.editor')) {
|
||||||
|
event.preventDefault(); // Empêcher l'action par défaut
|
||||||
|
const url = event.target.href;
|
||||||
|
console.log(`Redirection vers ${url}`); // Affiche l'URL dans la console
|
||||||
|
window.location.href = url; // Rediriger manuellement
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
export function initPages() {
|
||||||
|
document.querySelectorAll('.delete-page-btn').forEach(button => {
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
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 de la page.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createNewPage(titre, pageId) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("titre", titre);
|
||||||
|
params.append("pageId", pageId);
|
||||||
|
|
||||||
|
return fetch("/Projet/NouvellePageDirect", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded"
|
||||||
|
},
|
||||||
|
body: params.toString()
|
||||||
|
})
|
||||||
|
.then(response => response.json());
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
export function initSocketBloc() {
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const pageId = parseInt(params.get("id"));
|
||||||
|
if (!pageId) return;
|
||||||
|
|
||||||
|
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('data-metadata', data.metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.querySelectorAll('.editor').forEach(bloc => {
|
||||||
|
bloc.addEventListener('input', event => {
|
||||||
|
const blocId = event.target.getAttribute('data-id');
|
||||||
|
const content = event.target.textContent;
|
||||||
|
const type = event.target.getAttribute('data-type');
|
||||||
|
const metadata = event.target.getAttribute('data-metadata');
|
||||||
|
|
||||||
|
const modif = {
|
||||||
|
action: "update",
|
||||||
|
blocId,
|
||||||
|
content,
|
||||||
|
metadata,
|
||||||
|
type
|
||||||
|
};
|
||||||
|
|
||||||
|
socketBloc.send(JSON.stringify(modif));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
export function initTchat() {
|
||||||
|
const socketTchat = new WebSocket("ws://" + window.location.host + "/Projet/ws/tchat");
|
||||||
|
|
||||||
|
socketTchat.onmessage = event => {
|
||||||
|
const container = document.querySelector(".messages-container");
|
||||||
|
if (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) {
|
||||||
|
input.addEventListener("keydown", event => {
|
||||||
|
const login = document.getElementById("user-login").textContent;
|
||||||
|
if (event.key === "Enter" && input.value.trim() !== "") {
|
||||||
|
socketTchat.send(`${login} : ${input.value}`);
|
||||||
|
input.value = "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
export function autoResize(bloc) {
|
||||||
|
bloc.style.height = 'auto';
|
||||||
|
bloc.style.height = bloc.scrollHeight + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
export 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" });
|
||||||
|
}
|
||||||
|
|
@ -28,14 +28,16 @@
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.editor').forEach(function(bloc) {
|
||||||
|
renderBlocStyle(bloc);
|
||||||
|
});
|
||||||
|
|
||||||
ajouterBlocVideSiBesoin();
|
ajouterBlocVideSiBesoin();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fonction pour ajouter un nouvel événement à chaque bloc
|
// Fonction pour ajouter un nouvel événement à chaque bloc
|
||||||
function addBlocEvent(bloc) {
|
function addBlocEvent(bloc) {
|
||||||
|
bloc.addEventListener('keydown', async function(event) {
|
||||||
bloc.addEventListener('keydown', function(event) {
|
|
||||||
|
|
||||||
if (event.key === 'Enter' && !event.shiftKey) {
|
if (event.key === 'Enter' && !event.shiftKey) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
|
@ -43,21 +45,34 @@
|
||||||
const texte = currentBloc.innerText.trim();
|
const texte = currentBloc.innerText.trim();
|
||||||
|
|
||||||
if (texte.startsWith("/")) {
|
if (texte.startsWith("/")) {
|
||||||
// Gérer les commandes
|
const commandeGeree = await handleSlashCommand(currentBloc, texte);
|
||||||
handleSlashCommand(currentBloc, texte);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const blocId = currentBloc.getAttribute('data-id'); // Récupère l'ID du bloc
|
// Sinon, sauvegarde normale
|
||||||
const type = currentBloc.getAttribute('data-type');
|
sauvegarderBloc(currentBloc);
|
||||||
const metadata = currentBloc.getAttribute('metadata');
|
ajouterBlocVideSiBesoin();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
bloc.addEventListener('input', function () {
|
||||||
|
autoResize(bloc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sauvegarderBloc(bloc) {
|
||||||
|
const blocId = bloc.getAttribute('data-id');
|
||||||
|
const type = bloc.getAttribute('data-type');
|
||||||
|
const metadata = bloc.getAttribute('data-metadata');
|
||||||
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
let contenuTexte = currentBloc.innerHTML
|
let contenuTexte = bloc.innerHTML
|
||||||
.replace(/<br\s*\/?>/gi, '\n') // Remplace les <br> par \n
|
.replace(/<br\s*\/?>/gi, '\n')
|
||||||
.replace(/<b>/gi, '**') // Remplace <b> par **
|
.replace(/<b>/gi, '**')
|
||||||
.replace(/<\/b>/gi, '**') // Remplace </b> par **
|
.replace(/<\/b>/gi, '**')
|
||||||
.replace(/<i>/gi, '*') // Remplace <i> par *
|
.replace(/<i>/gi, '*')
|
||||||
.replace(/<\/i>/gi, '*'); // Remplace </i> par *
|
.replace(/<\/i>/gi, '*');
|
||||||
|
|
||||||
params.append("contenu", contenuTexte);
|
params.append("contenu", contenuTexte);
|
||||||
params.append("blocId", blocId);
|
params.append("blocId", blocId);
|
||||||
params.append("metadata", metadata);
|
params.append("metadata", metadata);
|
||||||
|
|
@ -74,15 +89,8 @@
|
||||||
console.error("Erreur lors de la mise à jour du bloc.");
|
console.error("Erreur lors de la mise à jour du bloc.");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ajouterBlocVideSiBesoin();
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
bloc.addEventListener('input', function () {
|
|
||||||
autoResize(bloc);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function autoResize(bloc) {
|
function autoResize(bloc) {
|
||||||
bloc.style.height = 'auto';
|
bloc.style.height = 'auto';
|
||||||
|
|
@ -109,6 +117,7 @@
|
||||||
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
|
||||||
|
params.append("metadata", "{}");
|
||||||
params.append("ordre", allBlocs.length);
|
params.append("ordre", allBlocs.length);
|
||||||
params.append("pageId", new URLSearchParams(window.location.search).get("id"));
|
params.append("pageId", new URLSearchParams(window.location.search).get("id"));
|
||||||
|
|
||||||
|
|
@ -135,7 +144,7 @@
|
||||||
newBloc.setAttribute('rows', '1');
|
newBloc.setAttribute('rows', '1');
|
||||||
newBloc.setAttribute('data-type', 'TEXTE');
|
newBloc.setAttribute('data-type', 'TEXTE');
|
||||||
newBloc.setAttribute('data-id', idGenere);
|
newBloc.setAttribute('data-id', idGenere);
|
||||||
newBloc.setAttribute('metadata', '{}');
|
newBloc.setAttribute('data-metadata', '{}');
|
||||||
|
|
||||||
control.appendChild(newBloc);
|
control.appendChild(newBloc);
|
||||||
container.appendChild(control);
|
container.appendChild(control);
|
||||||
|
|
@ -174,10 +183,10 @@
|
||||||
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('[contenteditable="true"], [contenteditable="false"]');
|
||||||
|
|
||||||
if (bloc.innerText.trim() === "") {
|
if (bloc.dataset.type != "SEPARATEUR" && bloc.innerText === "") {
|
||||||
return; // Empêche la suppression si le bloc est vide
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (confirm("Voulez-vous vraiment supprimer ce bloc ?")) {
|
if (confirm("Voulez-vous vraiment supprimer ce bloc ?")) {
|
||||||
|
|
@ -240,10 +249,12 @@
|
||||||
|
|
||||||
socketTchat.onmessage = function(event) {
|
socketTchat.onmessage = function(event) {
|
||||||
const container = document.querySelector(".messages-container");
|
const container = document.querySelector(".messages-container");
|
||||||
|
if (container) {
|
||||||
const p = document.createElement("p");
|
const p = document.createElement("p");
|
||||||
p.textContent = event.data;
|
p.textContent = event.data;
|
||||||
container.appendChild(p);
|
container.appendChild(p);
|
||||||
container.scrollTop = container.scrollHeight;
|
container.scrollTop = container.scrollHeight;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const input = document.querySelector("input[name='contenu']");
|
const input = document.querySelector("input[name='contenu']");
|
||||||
|
|
@ -274,7 +285,7 @@
|
||||||
const blocElement = document.querySelector(`.editor[data-id='${data.blocId}']`);
|
const blocElement = document.querySelector(`.editor[data-id='${data.blocId}']`);
|
||||||
if (blocElement) {
|
if (blocElement) {
|
||||||
blocElement.textContent = data.content;
|
blocElement.textContent = data.content;
|
||||||
blocElement.setAttribute('metadata', data.metadata);
|
blocElement.setAttribute('data-metadata', data.metadata);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -284,7 +295,7 @@
|
||||||
const blocId = event.target.getAttribute('data-id');
|
const blocId = event.target.getAttribute('data-id');
|
||||||
const content = event.target.textContent; // Récupère le contenu modifié du bloc
|
const content = event.target.textContent; // Récupère le contenu modifié du bloc
|
||||||
const type = event.target.getAttribute('data-type');
|
const type = event.target.getAttribute('data-type');
|
||||||
const metadata = event.target.getAttribute('metadata'); // Récupère le metadata du bloc
|
const metadata = event.target.getAttribute('data-metadata'); // Récupère le metadata du bloc
|
||||||
|
|
||||||
const modif = {
|
const modif = {
|
||||||
action: "update",
|
action: "update",
|
||||||
|
|
@ -310,16 +321,18 @@
|
||||||
|
|
||||||
|
|
||||||
// pour modifier le rendu des blocs
|
// pour modifier le rendu des blocs
|
||||||
function getBlocMetadata(bloc) {
|
function getBlocDataMetadata(bloc) {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(bloc.dataset.metadata || "{}");
|
return JSON.parse(bloc.getAttribute('data-metadata') || "{}");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setBlocMetadata(bloc, newMetadata) {
|
function setBlocMetadata(bloc, metadata) {
|
||||||
bloc.dataset.metadata = JSON.stringify(newMetadata);
|
const metadataStr = JSON.stringify(metadata);
|
||||||
|
bloc.dataset.metadata = metadataStr;
|
||||||
|
bloc.setAttribute('data-metadata', metadataStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyBlocType(bloc, type, extra = {}) {
|
function applyBlocType(bloc, type, extra = {}) {
|
||||||
|
|
@ -346,6 +359,8 @@
|
||||||
title: extra.title || "Nouvelle page"
|
title: extra.title || "Nouvelle page"
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
case "SEPARATEUR":
|
||||||
|
metadata = {};
|
||||||
default:
|
default:
|
||||||
console.warn("Type de bloc inconnu :", type);
|
console.warn("Type de bloc inconnu :", type);
|
||||||
}
|
}
|
||||||
|
|
@ -359,8 +374,8 @@
|
||||||
|
|
||||||
// Nettoyage des anciennes classes
|
// Nettoyage des anciennes classes
|
||||||
bloc.classList.remove('is-title', 'is-title-1', 'is-title-2', 'is-title-3');
|
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.classList.remove('is-code-block', 'is-list', 'is-page-link', 'is-separator');
|
||||||
bloc.style.whiteSpace = "normal"; // reset si code
|
bloc.style.whiteSpace = "normal"; // reset eventuel
|
||||||
|
|
||||||
const type = bloc.dataset.type || 'TEXTE';
|
const type = bloc.dataset.type || 'TEXTE';
|
||||||
|
|
||||||
|
|
@ -370,7 +385,8 @@
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'TITRE':
|
case 'TITRE':
|
||||||
const level = bloc.dataset.level || '1';
|
const metadata = getBlocDataMetadata(bloc);
|
||||||
|
const level = metadata.level || '1';
|
||||||
bloc.classList.add('is-title', `is-title-${level}`);
|
bloc.classList.add('is-title', `is-title-${level}`);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
@ -385,12 +401,12 @@
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'PAGE':
|
case 'PAGE':
|
||||||
bloc.classList.add('is-page-block');
|
bloc.classList.add('is-page-link');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'TOGGLE':
|
case 'SEPARATEUR':
|
||||||
bloc.classList.add('is-toggle');
|
bloc.classList.add('is-separator');
|
||||||
break;
|
bloc.contentEditable = "false";
|
||||||
|
|
||||||
default:
|
default:
|
||||||
bloc.classList.add('is-text');
|
bloc.classList.add('is-text');
|
||||||
|
|
@ -400,50 +416,76 @@
|
||||||
|
|
||||||
|
|
||||||
function handleSlashCommand(bloc, texte) {
|
function handleSlashCommand(bloc, texte) {
|
||||||
const parts = texte.substring(1).split(" "); // Supprimer le "/" et diviser
|
return new Promise((resolve) => {
|
||||||
|
const parts = texte.trim().substring(1).split(" ");
|
||||||
const command = parts[0].toLowerCase();
|
const command = parts[0].toLowerCase();
|
||||||
const param = parts.slice(1).join(" "); // Récupère les paramètres (par exemple "js" pour /code js)
|
const param = parts.slice(1).join(" ");
|
||||||
|
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case "page":
|
case "page":
|
||||||
createNewPage(param || "Nouvelle page");
|
createNewPage(param || "Nouvelle page").then(data => {
|
||||||
bloc.textContent = param || "Nouvelle page"; // On vide le bloc après transformation
|
if (data && data.id) {
|
||||||
|
const lien = `/Projet/AfficherPage?id=${data.id}`;
|
||||||
|
bloc.innerHTML = `<a href="${lien}">${param || "Nouvelle page"}</a>`;
|
||||||
|
applyBlocType(bloc, "PAGE", { pageId: data.id, title: param });
|
||||||
autoResize(bloc);
|
autoResize(bloc);
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
console.error("Erreur de création de page");
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
console.error("Erreur lors de la création de la page :", error);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "h1":
|
case "h1":
|
||||||
case "h2":
|
case "h2":
|
||||||
case "h3":
|
case "h3":
|
||||||
applyBlocType(bloc, "TITRE", { level: parseInt(command[1] || "1") });
|
applyBlocType(bloc, "TITRE", { level: parseInt(command[1] || "1") });
|
||||||
bloc.textContent = param; // On vide le bloc après transformation
|
bloc.textContent = param;
|
||||||
autoResize(bloc);
|
autoResize(bloc);
|
||||||
|
resolve();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "ul":
|
case "ul":
|
||||||
applyBlocType(bloc, "LISTE", { style: "bullet" });
|
applyBlocType(bloc, "LISTE", { style: "bullet" });
|
||||||
bloc.textContent = ''; // On vide le bloc après transformation
|
bloc.textContent = '';
|
||||||
autoResize(bloc);
|
autoResize(bloc);
|
||||||
|
resolve();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "ol":
|
case "ol":
|
||||||
applyBlocType(bloc, "LISTE", { style: "numbered" });
|
applyBlocType(bloc, "LISTE", { style: "numbered" });
|
||||||
bloc.textContent = ''; // On vide le bloc après transformation
|
bloc.textContent = '';
|
||||||
autoResize(bloc);
|
autoResize(bloc);
|
||||||
|
resolve();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "code":
|
case "code":
|
||||||
applyBlocType(bloc, "CODE", { language: param || "plaintext" });
|
applyBlocType(bloc, "CODE", { language: param || "plaintext" });
|
||||||
bloc.textContent = ''; // On vide le bloc après transformation
|
bloc.textContent = '';
|
||||||
autoResize(bloc);
|
autoResize(bloc);
|
||||||
|
resolve();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "hr":
|
||||||
|
applyBlocType(bloc, "SEPARATEUR", {}); // ou un type adapté selon ta logique
|
||||||
|
bloc.innerText = ' ';
|
||||||
|
autoResize(bloc);
|
||||||
|
resolve(true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.log(`Commande non reconnue: ${command}`);
|
console.log(`Commande non reconnue: ${command}`);
|
||||||
|
resolve();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
bloc.focus();
|
bloc.focus();
|
||||||
placeCursorAtEnd(bloc);
|
placeCursorAtEnd(bloc);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function placeCursorAtEnd(element) {
|
function placeCursorAtEnd(element) {
|
||||||
|
|
@ -463,25 +505,14 @@
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
params.append("titre", titre);
|
params.append("titre", titre);
|
||||||
|
|
||||||
fetch("/Projet/NouvellePage", {
|
return fetch("/Projet/NouvellePageDirect", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/x-www-form-urlencoded"
|
"Content-Type": "application/x-www-form-urlencoded"
|
||||||
},
|
},
|
||||||
body: params.toString()
|
body: params.toString()
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
.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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tchat-container {
|
.tchat-container {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
|
@ -27,7 +28,7 @@
|
||||||
/* pour les blocs */
|
/* pour les blocs */
|
||||||
.is-title-1 { font-size: 2em; font-weight: bold; }
|
.is-title-1 { font-size: 2em; font-weight: bold; }
|
||||||
.is-title-2 { font-size: 1.5em; font-weight: bold; }
|
.is-title-2 { font-size: 1.5em; font-weight: bold; }
|
||||||
.is-title-3 { font-size: 1.2em; font-weight: bold; }
|
.is-title-3 { font-size: 1em; font-weight: bold; }
|
||||||
|
|
||||||
.is-code-block {
|
.is-code-block {
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
|
|
@ -41,7 +42,39 @@
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
.is-toggle::before {
|
.is-separator {
|
||||||
content: "▶ ";
|
border-top: 1px solid #9a9996;
|
||||||
cursor: pointer;
|
height: 1px;
|
||||||
|
width: 100%;
|
||||||
|
background: none;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-page-link:hover {
|
||||||
|
background-color: #e1e7f0;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-quote {
|
||||||
|
padding-left: 10px;
|
||||||
|
border-left: 2px solid #ccc;
|
||||||
|
margin-left: 10px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-quote-normal {
|
||||||
|
border-left-color: #ccc;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-quote-danger {
|
||||||
|
border-left-color: #e74c3c;
|
||||||
|
background-color: #fdecea;
|
||||||
|
color: #c0392b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-quote-info {
|
||||||
|
border-left-color: #3498db;
|
||||||
|
background-color: #eaf6fb;
|
||||||
|
color: #2980b9;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue