Export : OK !!

This commit is contained in:
Lensors 2025-05-02 18:34:45 +02:00
parent da3140f007
commit db919b8902
24 changed files with 1037 additions and 435 deletions

View File

@ -14,5 +14,7 @@
<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.web.container"/> <classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.web.container"/>
<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.module.container"/> <classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.module.container"/>
<classpathentry excluding="main/java/" kind="src" path="src"/> <classpathentry excluding="main/java/" kind="src" path="src"/>
<classpathentry kind="lib" path="src/main/webapp/WEB-INF/lib/javax.json-1.1.4.jar"/>
<classpathentry kind="lib" path="src/main/webapp/WEB-INF/lib/javax.json-api-1.1.4.jar"/>
<classpathentry kind="output" path="build/classes"/> <classpathentry kind="output" path="build/classes"/>
</classpath> </classpath>

View File

@ -1,10 +1,13 @@
package projet; package projet;
import java.io.StringReader;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObject;
import jakarta.websocket.*; import jakarta.websocket.*;
import jakarta.websocket.server.ServerEndpoint; import jakarta.websocket.server.ServerEndpoint;
@ -14,6 +17,8 @@ import jakarta.websocket.server.ServerEndpoint;
public class BlocCollaborative { public class BlocCollaborative {
private static final Map<String, Set<Session>> sessionsParPage = new ConcurrentHashMap<>(); private static final Map<String, Set<Session>> sessionsParPage = new ConcurrentHashMap<>();
private static final Map<String, Map<Session, String>> utilisateursParPage = new ConcurrentHashMap<>();
@OnOpen @OnOpen
public void onOpen(Session session) { public void onOpen(Session session) {
@ -28,18 +33,22 @@ public class BlocCollaborative {
public void onMessage(String message, Session session) { public void onMessage(String message, Session session) {
String pageId = (String) session.getUserProperties().get("pageId"); String pageId = (String) session.getUserProperties().get("pageId");
Set<Session> sessions = sessionsParPage.get(pageId); Set<Session> sessions = sessionsParPage.get(pageId);
Map<Session, String> utilisateurs = utilisateursParPage.computeIfAbsent(pageId, k -> new ConcurrentHashMap<>());
Lock sessionLock = new ReentrantLock(); JsonObject json = Json.createReader(new StringReader(message)).readObject();
if ("nouvelleConnexion".equals(json.getString("action", ""))) {
String login = json.getString("utilisateur", "Anonyme");
utilisateurs.put(session, login);
envoiUtilisateursParPage(pageId);
return;
}
synchronized (sessions) { synchronized (sessions) {
for (Session s : sessions) { for (Session s : sessions) {
if (s.isOpen() && !s.equals(session)) { if (s.isOpen() && !s.equals(session)) {
sessionLock.lock(); s.getAsyncRemote().sendText(message);
try {
s.getAsyncRemote().sendText(message);
} finally {
sessionLock.unlock();
}
} }
} }
} }
@ -50,12 +59,18 @@ public class BlocCollaborative {
String pageId = (String) session.getUserProperties().get("pageId"); String pageId = (String) session.getUserProperties().get("pageId");
if (pageId != null) { if (pageId != null) {
Set<Session> sessions = sessionsParPage.get(pageId); Set<Session> sessions = sessionsParPage.get(pageId);
Map<Session, String> utilisateurs = utilisateursParPage.get(pageId);
if (sessions != null) { if (sessions != null) {
sessions.remove(session); sessions.remove(session);
if (sessions.isEmpty()) { if (sessions.isEmpty()) {
sessionsParPage.remove(pageId); sessionsParPage.remove(pageId);
utilisateursParPage.remove(pageId);
} }
} }
if (utilisateurs != null) {
utilisateurs.remove(session);
envoiUtilisateursParPage(pageId);
}
} }
} }
@ -73,4 +88,28 @@ public class BlocCollaborative {
} }
return "default"; return "default";
} }
private void envoiUtilisateursParPage(String pageId) {
Set<Session> sessions = sessionsParPage.get(pageId);
Map<Session, String> utilisateurs = utilisateursParPage.get(pageId);
if (utilisateurs == null || sessions == null) return;
JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
for (String utilisateur : utilisateurs.values()) {
arrayBuilder.add(utilisateur);
}
JsonObject json = Json.createObjectBuilder()
.add("action", "users")
.add("users", arrayBuilder)
.build();
for (Session s : sessions) {
if (s.isOpen()) {
s.getAsyncRemote().sendText(json.toString());
}
}
}
} }

View File

@ -1,31 +1,50 @@
package projet; package projet;
import java.io.StringReader;
import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonReader;
public interface BlocRenderer { public interface BlocRenderer {
String renderTexte(String contenu); String renderTexte(String contenu);
String renderListe(String contenu); String renderListe(String contenu, String style);
String renderTitre(String contenu); String renderTitre(String contenu, int level);
String renderCode(String contenu); String renderCode(String contenu, String language);
String renderPage(String contenu); String renderPage(String title, String from);
String renderSeparateur(); String renderSeparateur();
String renderCitation(String contenu); String renderCitation(String contenu, String type);
default JsonObject parseMetadata(String metadata) {
try (JsonReader reader = Json.createReader(new StringReader(metadata))) {
return reader.readObject();
}
}
default String render(Bloc bloc) { default String render(Bloc bloc) {
JsonObject metadata = parseMetadata(bloc.getMetadata());
switch (bloc.getType()) { switch (bloc.getType()) {
case TEXTE: case TEXTE:
return renderTexte(bloc.getContenu()); return renderTexte(bloc.getContenu());
case LISTE: case LISTE:
return renderListe(bloc.getContenu()); String style = metadata.getString("style", "bullet");
return renderListe(bloc.getContenu(), style);
case TITRE: case TITRE:
return renderTitre(bloc.getContenu()); int level = metadata.getInt("level", 1);
return renderTitre(bloc.getContenu(), level);
case CODE: case CODE:
return renderCode(bloc.getContenu()); String language = metadata.getString("language", "");
return renderCode(bloc.getContenu(), language);
case PAGE: case PAGE:
return renderPage(bloc.getContenu()); String title = metadata.getString("title", "Page");
String from = metadata.getString("from", "");
return renderPage(title, from);
case SEPARATEUR: case SEPARATEUR:
return renderSeparateur(); return renderSeparateur();
case CITATION: case CITATION:
return renderCitation(bloc.getContenu()); String type = metadata.getString("type", "normal");
return renderCitation(bloc.getContenu(), type);
default: default:
throw new IllegalArgumentException("Type de bloc inconnu : " + bloc.getType()); throw new IllegalArgumentException("Type de bloc inconnu : " + bloc.getType());
} }

View File

@ -0,0 +1,95 @@
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.util.ArrayList;
@WebServlet("/Export")
public class Export extends HttpServlet {
private static final long serialVersionUID = 1L;
public Export() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
HttpSession session = request.getSession();
Utilisateur u = (Utilisateur) session.getAttribute("utilisateur");
String idStr = request.getParameter("id");
String type = request.getParameter("type");
if(u != null && idStr != null && type != null) {
u.chargerPages();
u.chargerPagesPartagees();
ArrayList<Page> listePages = u.getListePages();
listePages.addAll(u.getListePagesPartagees());
try {
int id = Integer.parseInt(idStr);
Boolean estDans = false;
for (Page page : listePages) {
if (page.getId() == id) {
estDans = true;
break;
}
}
if (!estDans) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Page introuvable.");
return;
}
Page pageToExport = Page.getPageById(u.getId(), id);
BlocRenderer blocRenderer;
PageRender pageRender;
String filename;
switch (type.toLowerCase()) {
case "html":
blocRenderer = new HtmlBlocRenderer();
pageRender = new HtmlPageRender();
response.setContentType("text/html");
filename = pageToExport.getTitre() + ".html";
break;
case "latex":
blocRenderer = new LatexBlocRenderer();
pageRender = new LatexPageRender();
response.setContentType("application/x-latex");
filename = pageToExport.getTitre() + ".tex";
break;
case "markdown":
blocRenderer = new MarkdownBlocRenderer();
pageRender = new MarkdownPageRender();
response.setContentType("text/markdown");
filename = pageToExport.getTitre() + ".md";
break;
default:
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Type de format non pris en charge.");
return;
}
String exportContent = pageRender.render(blocRenderer, pageToExport.getListeBlocs());
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");
response.getWriter().write(exportContent);
return;
} catch (NumberFormatException e) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "ID invalide");
return;
}
} else {
response.sendRedirect("/Projet/");
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}

View File

@ -0,0 +1,69 @@
package projet;
public class HtmlBlocRenderer implements BlocRenderer {
@Override
public String renderTexte(String contenu) {
contenu = escapeHtml(contenu);
return "<p>" + contenu + "</p>\n";
}
@Override
public String renderListe(String contenu, String style) {
contenu = escapeHtml(contenu);
StringBuilder sb = new StringBuilder();
String[] items = contenu.split("\n");
String tag = "bullet".equals(style) ? "ul" : "ol";
sb.append("<").append(tag).append(">\n");
for (String item : items) {
sb.append(" <li>").append(item).append("</li>\n");
}
sb.append("</").append(tag).append(">\n");
return sb.toString();
}
@Override
public String renderTitre(String contenu, int level) {
contenu = escapeHtml(contenu);
level = Math.max(1, Math.min(level, 6));
return "<h" + level + ">" + contenu + "</h" + level + ">\n";
}
@Override
public String renderCode(String contenu, String language) {
contenu = escapeHtml(contenu);
return "<pre><code class=\"" + language + "\">\n" + contenu + "\n</code></pre>\n";
}
@Override
public String renderPage(String title, String from) {
return "<a href=\"page:" + from + "\">" + title + "</a>\n";
}
@Override
public String renderSeparateur() {
return "<hr/>\n";
}
@Override
public String renderCitation(String contenu, String type) {
contenu = escapeHtml(contenu);
return "<blockquote class=\"" + type + "\">\n" + contenu + "\n</blockquote>\n";
}
public static String escapeHtml(String content) {
if (content == null) return null;
content = content.replaceAll("\n", "<br />");
content = content.replaceAll("\\*\\*(.*?)\\*\\*", "<b>$1</b>");
content = content.replaceAll("\\*(.*?)\\*", "<i>$1</i>");
content = content.replaceAll("`(.*?)`", "<code>$1</code>");
return content;
}
}

View File

@ -0,0 +1,21 @@
package projet;
public class HtmlPageRender implements PageRender {
@Override
public String getHeader() {
return "<!DOCTYPE html>\n"
+ "<html>\n"
+ "<head>\n"
+ "<meta charset=\"UTF-8\">\n"
+ "<title>Page</title>\n"
+ "</head>\n"
+ "<body>\n";
}
@Override
public String getFooter() {
return "</body>\n"
+ "</html>\n";
}
}

View File

@ -0,0 +1,71 @@
package projet;
public class LatexBlocRenderer implements BlocRenderer {
@Override
public String renderTexte(String contenu) {
contenu = escapeLatex(contenu);
return contenu + "\n\n";
}
@Override
public String renderListe(String contenu, String style) {
StringBuilder sb = new StringBuilder();
String env = "bullet".equals(style) ? "itemize" : "enumerate";
sb.append("\\begin{").append(env).append("}\n");
for (String item : contenu.split("\n")) {
sb.append(" \\item ").append(item).append("\n");
}
sb.append("\\end{").append(env).append("}\n\n");
return sb.toString();
}
@Override
public String renderTitre(String contenu, int level) {
contenu = escapeLatex(contenu);
switch (level) {
case 1: return "\\section{" + contenu + "}\n\n";
case 2: return "\\subsection{" + contenu + "}\n\n";
case 3: return "\\subsubsection{" + contenu + "}\n\n";
default: return "\\paragraph{" + contenu + "}\n\n";
}
}
@Override
public String renderCode(String contenu, String language) {
contenu = escapeLatex(contenu);
return "\\begin{verbatim}\n" + contenu + "\n\\end{verbatim}\n\n";
}
@Override
public String renderPage(String title, String from) {
return "\\href{page:" + from + "}{" + title + "}\n\n";
}
@Override
public String renderSeparateur() {
return "\\noindent\\rule{\\linewidth}{0.4pt}\n\n";
}
@Override
public String renderCitation(String contenu, String type) {
contenu = escapeLatex(contenu);
return "\\begin{quote}\n" + contenu + "\n\\end{quote}\n\n";
}
public static String escapeLatex(String content) {
if (content == null) return null;
content = content.replaceAll("\n", "\\\\\\\\ \n");
content = content.replaceAll("\\*\\*(.*?)\\*\\*", "\\\\textbf{$1}");
content = content.replaceAll("\\*(.*?)\\*", "\\\\textit{$1}");
content = content.replaceAll("`(.*?)`", "\\\\texttt{$1}");
return content;
}
}

View File

@ -0,0 +1,17 @@
package projet;
public class LatexPageRender implements PageRender {
@Override
public String getHeader() {
return "\\documentclass{article}\n"
+ "\\usepackage[utf8]{inputenc}\n"
+ "\\usepackage{hyperref}\n"
+ "\\begin{document}\n";
}
@Override
public String getFooter() {
return "\\end{document}\n";
}
}

View File

@ -0,0 +1,55 @@
package projet;
public class MarkdownBlocRenderer implements BlocRenderer {
@Override
public String renderTexte(String contenu) {
return contenu + "\n";
}
@Override
public String renderListe(String contenu, String style) {
String[] items = contenu.split("\n");
StringBuilder sb = new StringBuilder();
for (int i = 0; i < items.length; i++) {
if ("number".equals(style)) {
sb.append(i + 1).append(". ").append(items[i]).append("\n");
} else {
sb.append("- ").append(items[i]).append("\n");
}
}
return sb.toString();
}
@Override
public String renderTitre(String contenu, int level) {
return "#".repeat(Math.max(1, level)) + " " + contenu + "\n";
}
@Override
public String renderCode(String contenu, String language) {
return "```" + language + "\n" + contenu + "\n```\n";
}
@Override
public String renderPage(String title, String from) {
return "[" + title + "](page:" + from + ")\n";
}
@Override
public String renderSeparateur() {
return "---\n";
}
@Override
public String renderCitation(String contenu, String type) {
StringBuilder result = new StringBuilder();
String[] lignes = contenu.split("\n");
for (String ligne : lignes) {
result.append("> ").append(ligne).append("\n");
}
return result.toString();
}
}

View File

@ -0,0 +1,14 @@
package projet;
public class MarkdownPageRender implements PageRender {
@Override
public String getHeader() {
return "";
}
@Override
public String getFooter() {
return "";
}
}

View File

@ -9,9 +9,7 @@ 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.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import projet.Bloc.Type; import projet.Bloc.Type;
@ -198,11 +196,13 @@ public class Page extends ParamBD {
+ " FROM partage" + " FROM partage"
+ " JOIN page ON partage.page_id = page.id" + " JOIN page ON partage.page_id = page.id"
+ " WHERE partage.utilisateur_id = ?" + " WHERE partage.utilisateur_id = ?"
+ " AND page.id = ?"
+ ";"; + ";";
pst = connexion.prepareStatement(sql); pst = connexion.prepareStatement(sql);
pst.setInt(1, id); pst.setInt(1, id);
pst.setInt(2, idU); pst.setInt(2, idU);
pst.setInt(3, idU); pst.setInt(3, idU);
pst.setInt(4, id);
rs = pst.executeQuery(); rs = pst.executeQuery();
while(rs.next()) { while(rs.next()) {
titre = rs.getString("titre"); titre = rs.getString("titre");

View File

@ -0,0 +1,19 @@
package projet;
import java.util.List;
public interface PageRender {
String getHeader();
String getFooter();
default String render(BlocRenderer blocRenderer, List<Bloc> blocs) {
StringBuilder sb = new StringBuilder();
sb.append(getHeader());
for (Bloc bloc : blocs) {
sb.append(blocRenderer.render(bloc)).append("\n");
}
sb.append(getFooter());
return sb.toString();
}
}

View File

@ -18,31 +18,50 @@
<div class="column is-half"> <div class="column is-half">
<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-center mb-4"> <div class="is-flex is-justify-content-space-between is-align-items mb-4">
<h2 class="block"> <h2 class="block">
<c:forEach var="entry" items="${hierarchie}"> <c:forEach var="entry" items="${hierarchie}">
<a href="AfficherPage?id=${entry.key}">${entry.value}</a> > <a href="AfficherPage?id=${entry.key}">${entry.value}</a> >
</c:forEach> </c:forEach>
${page.titre} ${page.titre}
</h2> </h2>
<div class="dropdown is-right"> <div>
<div class="dropdown-trigger"> <div class="dropdown is-right is-inline-flex mr-2 is-hoverable">
<button class="button" aria-haspopup="true" aria-controls="dropdown-menu3"> <div class="dropdown-trigger">
<span><i class="fa-solid fa-share-nodes"></i></span> <button class="button" aria-haspopup="true" aria-controls="dropdown-menu3">
<span class="icon is-small"> <span><i class="fa-solid fa-file-export"></i></span>
<i class="fas fa-angle-down" aria-hidden="true"></i> <span class="icon is-small">
</span> <i class="fas fa-angle-down" aria-hidden="true"></i>
</button> </span>
</div> </button>
<div class="dropdown-menu" id="dropdown-menu3" role="menu"> </div>
<div class="dropdown-content"> <div class="dropdown-menu" id="dropdown-menu3" role="menu">
<c:forEach var="u" items="${listeUtilisateurs}"> <div class="dropdown-content">
<c:if test="${u.id != utilisateur.id}"> <a href="Export?id=${page.id}&type=latex" class="dropdown-item">Latex</a>
<a href="Partage?idP=${u.id}&idPage=${page.id}" class="dropdown-item">${u.login}</a> <a href="Export?id=${page.id}&type=markdown" class="dropdown-item">Markdown</a>
</c:if> <a href="Export?id=${page.id}&type=html" class="dropdown-item">HTLM</a>
</c:forEach> </div>
</div> </div>
</div> </div>
<div class="dropdown is-right is-inline-flex is-hoverable">
<div class="dropdown-trigger">
<button class="button" aria-haspopup="true" aria-controls="dropdown-menu3">
<span><i class="fa-solid fa-share-nodes"></i></span>
<span class="icon is-small">
<i class="fas fa-angle-down" aria-hidden="true"></i>
</span>
</button>
</div>
<div class="dropdown-menu" id="dropdown-menu3" role="menu">
<div class="dropdown-content">
<c:forEach var="u" items="${listeUtilisateurs}">
<c:if test="${u.id != utilisateur.id}">
<a href="Partage?idP=${u.id}&idPage=${page.id}" class="dropdown-item">${u.login}</a>
</c:if>
</c:forEach>
</div>
</div>
</div>
</div> </div>
</div> </div>
<div class="block" id="md"> <div class="block" id="md">

View File

@ -22,6 +22,18 @@
<div class="navbar-menu"> <div class="navbar-menu">
<div class="navbar-end"> <div class="navbar-end">
<c:if test="${not empty utilisateur}"> <c:if test="${not empty utilisateur}">
<!-- Dropdown utilisateurs connectés -->
<c:if test="${not empty page.titre}">
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link">
<i class="fa-solid fa-users"></i>
<span style="margin-left: 5px;">Connectés</span>
</a>
<div class="navbar-dropdown is-right" id="connected-users-dropdown">
<div class="navbar-item"></div>
</div>
</div>
</c:if>
<div class="navbar-item"> <div class="navbar-item">
<p id="user-login">${utilisateur.login}</p> <p id="user-login">${utilisateur.login}</p>
</div> </div>

View File

@ -1,11 +1,16 @@
import { autoResize, placeCursorAtEnd } from './utils.js'; import { autoResize, creerBlocDOM } from './utils.js';
import { createNewPage } from './page.js';
import { activerCollagePropre } from './collagePropre.js';
import { renderBlocStyle,
handleSlashCommand,
handleMarkdownSyntax} from './functionsBloc.js'
export function initBlocs() { export function initBlocs() {
document.querySelectorAll('#md [contenteditable="true"]').forEach(bloc => { document.querySelectorAll('.editor').forEach(bloc => {
addBlocEvent(bloc); addBlocEvent(bloc);
renderBlocStyle(bloc); renderBlocStyle(bloc);
autoResize(bloc); autoResize(bloc);
activerCollagePropre(bloc);
}); });
document.querySelectorAll('.editor a').forEach(link => { document.querySelectorAll('.editor a').forEach(link => {
@ -44,6 +49,25 @@ function formatEditorContent() {
} }
export function addBlocEvent(bloc) { export function addBlocEvent(bloc) {
bloc.addEventListener('input', event => {
const blocId = event.target.getAttribute('data-id');
const content = event.target.innerHTML;
const type = event.target.getAttribute('data-type');
const metadata = event.target.getAttribute('data-metadata');
const modif = {
action: "update",
blocId,
content,
metadata,
type
};
if (window.socketBloc && window.socketBloc.readyState === WebSocket.OPEN) {
window.socketBloc.send(JSON.stringify(modif));
}
});
bloc.addEventListener('keydown', async event => { bloc.addEventListener('keydown', async event => {
if (event.key === 'Enter' && !event.shiftKey) { if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault(); event.preventDefault();
@ -59,6 +83,53 @@ export function addBlocEvent(bloc) {
ajouterBlocVideSiBesoin(); ajouterBlocVideSiBesoin();
formatEditorContent(); formatEditorContent();
focusDernierBloc(); focusDernierBloc();
const blocId = bloc.getAttribute('data-id');
const content = bloc.innerHTML;
const type = bloc.getAttribute('data-type');
const classBloc = bloc.getAttribute('class');
const metadata = bloc.getAttribute('data-metadata');
const modif = {
action: "actualiserMiseEnPageBloc",
blocId,
content,
metadata,
classBloc,
type
};
socketBloc.send(JSON.stringify(modif));
}
// Si on est dans une liste, on crée un nouveau li
if (event.key === 'Enter' && event.shiftKey) {
const isListe = bloc.dataset.type === "LISTE";
if (isListe) {
event.preventDefault();
// Trouver le <li> courant
const selection = window.getSelection();
const range = selection.getRangeAt(0);
const li = (range.startContainer.nodeType === Node.ELEMENT_NODE
? range.startContainer
: range.startContainer.parentNode
).closest('li');
if (li) {
// Créer un nouvel <li> vide
const newLi = document.createElement('li');
newLi.innerHTML = '<br>'; // pour que le li soit focusable
li.parentNode.insertBefore(newLi, li.nextSibling);
// Placer le curseur dans le nouveau li
const newRange = document.createRange();
newRange.setStart(newLi, 0);
newRange.collapse(true);
selection.removeAllRanges();
selection.addRange(newRange);
autoResize(bloc); // redimensionne si nécessaire
}
}
} }
}); });
@ -78,7 +149,9 @@ export function sauvegarderBloc(bloc) {
.replace(/<i>/gi, '*') .replace(/<i>/gi, '*')
.replace(/<\/i>/gi, '*') .replace(/<\/i>/gi, '*')
.replace(/<code>/gi, '`') .replace(/<code>/gi, '`')
.replace(/<\/code>/gi, '`'); .replace(/<\/code>/gi, '`')
.replace(/<\/li>/gi, '\n')
.replace(/<\/?[^>]+(>|$)/g, ""); // Supprime les balises restantes
params.append("contenu", contenuTexte); params.append("contenu", contenuTexte);
params.append("blocId", blocId); params.append("blocId", blocId);
@ -152,38 +225,25 @@ export function ajouterBlocVideSiBesoin() {
.then(data => { .then(data => {
if (data && data.idGenere) { if (data && data.idGenere) {
const idGenere = data.idGenere; const idGenere = data.idGenere;
const newBloc = creerBlocDOM({
blocId: idGenere,
content: "",
type: "TEXTE",
metadata: "{}",
ordre: allBlocs.length
});
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(); newBloc.focus();
// Envoi d'une notification d'un nouveau bloc créé via websocket
window.socketBloc.send(JSON.stringify({
action: "newBloc",
blocId: idGenere,
content: "",
type: "TEXTE",
metadata: "{}",
ordre: allBlocs.length
}));
} }
}); });
} }
@ -197,237 +257,3 @@ export function focusDernierBloc() {
} }
} }
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);
}

View File

@ -0,0 +1,22 @@
export function activerCollagePropre(elementEditable) {
elementEditable.addEventListener('paste', function (e) {
e.preventDefault();
// Récupère le texte brut depuis le presse-papiers
const texte = (e.clipboardData || window.clipboardData).getData('text/plain');
// Insertion propre avec les API de sélection moderne
const selection = window.getSelection();
if (!selection.rangeCount) return;
const range = selection.getRangeAt(0);
range.deleteContents();
range.insertNode(document.createTextNode(texte));
// Replace le curseur à la fin du texte collé
range.collapse(false);
selection.removeAllRanges();
selection.addRange(range);
});
}

View File

@ -1,114 +0,0 @@
// 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 lordre');
}
});
}

View File

@ -0,0 +1,271 @@
import { autoResize, placeCursorAtEnd } from './utils.js'
import { createNewPage } from './page.js';
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);
}
export function renderBlocStyle(bloc) {
if (!bloc || !bloc.dataset) return;
clearBlocStyles(bloc);
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':
renderListe(bloc);
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);
}
export function clearBlocStyles(bloc) {
bloc.classList.remove(
'is-title', 'is-title-1', 'is-title-2', 'is-title-3',
'is-quote', 'is-quote-info', 'is-quote-warning', 'is-quote-normal',
'is-code-block', 'is-list', 'is-page-link', 'is-separator', 'is-text'
);
bloc.style.whiteSpace = "normal";
}
export function renderListe(bloc) {
const metadata = getBlocDataMetadata(bloc);
const style = metadata.style || 'bullet';
const tag = style === 'number' ? 'ol' : 'ul';
const lignes = bloc.textContent.split('\n').filter(line => line.trim() !== '');
const items = lignes.map(line => `<li>${line.trim()}</li>`).join('');
bloc.innerHTML = `<${tag}>${items}</${tag}>`;
}
export 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);
});
}
export async function handleMarkdownSyntax(bloc, texte) {
// Vérifier si c'est un titre
if (texte.startsWith('#')) {
const level = texte.split(' ')[0].length; // Compter le nombre de # pour le niveau du titre
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);
}
// Vérifier si c'est une liste
else if (texte.match(/^(\s*([-*+])\s+.+|\s*\d+\.\s+.+)(\n|$)/m)) {
const lignes = texte.split('\n').filter(line => line.trim() !== '');
let style = 'bullet';
if (lignes.every(line => line.trim().match(/^\d+\.\s+/))) {
style = 'number';
} else if (lignes.every(line => line.trim().match(/^[-*+]\s+/))) {
style = 'bullet';
} else {
return; // Pas une vraie liste structurée
}
const contenu = lignes.map(line => line.replace(/^(\d+\.\s+|[-*+]\s+)/, '')).join('\n');
applyBlocType(bloc, "LISTE", { style: style });
bloc.textContent = contenu;
renderListe(bloc);
autoResize(bloc);
}
}
export function getBlocDataMetadata(bloc) {
try {
return JSON.parse(bloc.getAttribute('data-metadata') || "{}");
} catch (e) {
return {};
}
}
export function setBlocMetadata(bloc, metadata) {
const metadataStr = JSON.stringify(metadata);
bloc.dataset.metadata = metadataStr;
bloc.setAttribute('data-metadata', metadataStr);
}
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;
}

View File

@ -1,16 +1,13 @@
import { initBlocs, ajouterBlocVideSiBesoin, focusDernierBloc } from './bloc.js'; import { initBlocs, ajouterBlocVideSiBesoin, focusDernierBloc } from './bloc.js';
import { initTchat } from './tchat.js'; import { initTchat } from './tchat.js';
import { initPages } from './page.js'; import { initPages } from './page.js';
import { initDropdowns } from './dropdown.js';
import { initSocketBloc } from './socket-bloc.js'; import { initSocketBloc } from './socket-bloc.js';
import { initializeDragDrop } from './drag-and-drop.js';
window.addEventListener('DOMContentLoaded', () => { window.addEventListener('DOMContentLoaded', () => {
initBlocs();
initDropdowns();
initTchat(); initTchat();
initPages(); initPages();
initSocketBloc(); initSocketBloc();
initBlocs();
ajouterBlocVideSiBesoin(); ajouterBlocVideSiBesoin();
focusDernierBloc(); focusDernierBloc();

View File

@ -1,25 +1,66 @@
import { autoResize, creerBlocDOM } from './utils.js'
import { renderBlocStyle, renderListe } from './functionsBloc.js'
export function initSocketBloc() { export function initSocketBloc() {
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
const pageId = parseInt(params.get("id")); const pageId = parseInt(params.get("id"));
if (!pageId) return; if (!pageId) return;
const socketBloc = new WebSocket("ws://" + window.location.host + `/Projet/ws/bloc?pageId=${pageId}`); const socketBloc = new WebSocket("ws://" + window.location.host + `/Projet/ws/bloc?pageId=${pageId}`);
window.socketBloc = socketBloc
socketBloc.onopen = () => {
const login = document.getElementById("user-login").textContent;
socketBloc.send(JSON.stringify({
action: "nouvelleConnexion",
utilisateur: login
}));
};
socketBloc.onmessage = event => { socketBloc.onmessage = event => {
const data = JSON.parse(event.data); const data = JSON.parse(event.data);
if (data.action === "update") { if (data.action === "update") {
const blocElement = document.querySelector(`.editor[data-id='${data.blocId}']`); const blocElement = document.querySelector(`.editor[data-id='${data.blocId}']`);
if (blocElement) { if (blocElement) {
blocElement.textContent = data.content; blocElement.innerHTML = data.content;
blocElement.setAttribute('data-metadata', data.metadata); blocElement.setAttribute('data-metadata', data.metadata);
autoResize(blocElement);
} }
} } else if (data.action === "users") {
const dropdown = document.getElementById("connected-users-dropdown");
if (dropdown === null) return;
dropdown.innerHTML = "";
data.users.forEach(user => {
const item = document.createElement("div");
item.className = "navbar-item";
item.textContent = user;
dropdown.appendChild(item);
});
} else if (data.action === "newBloc") {
creerBlocDOM({
blocId: data.blocId,
content: data.content,
type: data.type,
metadata: data.metadata || "{}",
ordre: data.ordre
});
} else if (data.action === "actualiserMiseEnPageBloc") {
const blocElement = document.querySelector(`.editor[data-id='${data.blocId}']`);
if (blocElement) {
blocElement.dataset.type = data.type;
blocElement.innerHTML = data.content;
blocElement.dataset.metadata = JSON.stringify(data.metadata || {});
blocElement.className = data.classBloc;
renderBloc(blocElement, blocElement.dataset.metadata);
}
}
}; };
document.querySelectorAll('.editor').forEach(bloc => { document.querySelectorAll('.editor').forEach(bloc => {
bloc.addEventListener('input', event => { bloc.addEventListener('input', event => {
const blocId = event.target.getAttribute('data-id'); const blocId = event.target.getAttribute('data-id');
const content = event.target.textContent; const content = event.target.innerHTML;
const type = event.target.getAttribute('data-type'); const type = event.target.getAttribute('data-type');
const metadata = event.target.getAttribute('data-metadata'); const metadata = event.target.getAttribute('data-metadata');
@ -35,3 +76,49 @@ export function initSocketBloc() {
}); });
}); });
} }
export function renderBloc(bloc, metadata) {
if (!bloc || !bloc.dataset) return;
const type = bloc.dataset.type || 'TEXTE';
switch (type) {
case 'TEXTE':
bloc.classList.add('is-text');
break;
case 'TITRE':
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 citationType = metadataCitation.type || 'normal';
bloc.classList.add('is-quote', `is-quote-${citationType}`);
break;
default:
bloc.classList.add('is-text');
break;
}
autoResize(bloc);
}

View File

@ -1,3 +1,7 @@
import { addDeleteBloc, addBlocEvent } from './bloc.js'
import { activerCollagePropre } from './collagePropre.js'
export function autoResize(bloc) { export function autoResize(bloc) {
bloc.style.height = 'auto'; bloc.style.height = 'auto';
bloc.style.height = bloc.scrollHeight + 'px'; bloc.style.height = bloc.scrollHeight + 'px';
@ -15,3 +19,49 @@ export function placeCursorAtEnd(element) {
element.scrollIntoView({ behavior: "smooth", block: "end" }); element.scrollIntoView({ behavior: "smooth", block: "end" });
} }
export function creerBlocDOM({ blocId, content = "", type = "TEXTE", metadata = "{}", ordre = null }) {
const container = document.createElement('div');
container.classList.add('field', 'is-grouped', 'is-align-items-flex-start', 'bloc-container');
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', type);
newBloc.setAttribute('data-id', blocId);
newBloc.setAttribute('data-metadata', metadata);
newBloc.innerHTML = content;
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', blocId);
delBtnWrapper.appendChild(delBtn);
container.appendChild(delBtnWrapper);
// Insertion à la bonne position
const parent = document.getElementById('md');
const blocs = parent.querySelectorAll('.bloc-container');
if (ordre === null || ordre >= blocs.length) {
parent.appendChild(container);
} else {
parent.insertBefore(container, blocs[ordre]);
}
// Activation comportements
addDeleteBloc(delBtn);
addBlocEvent(newBloc);
activerCollagePropre(newBloc);
autoResize(newBloc);
return newBloc;
}

View File

@ -37,9 +37,20 @@
padding: 0.5em; padding: 0.5em;
} }
.is-list::before { .is-list ul,
content: "• "; .is-list ol {
color: #666; list-style-position: inside;
margin-left: 1em;
padding-left: 1em;
color: #666;
}
.is-list ul {
list-style-type: disc;
}
.is-list ol {
list-style-type: decimal;
} }
.is-separator { .is-separator {