Export : OK !!
This commit is contained in:
parent
da3140f007
commit
db919b8902
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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();
|
|
||||||
try {
|
|
||||||
s.getAsyncRemote().sendText(message);
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package projet;
|
||||||
|
|
||||||
|
public class MarkdownPageRender implements PageRender {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHeader() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFooter() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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");
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,14 +18,32 @@
|
||||||
<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 is-right is-inline-flex mr-2 is-hoverable">
|
||||||
|
<div class="dropdown-trigger">
|
||||||
|
<button class="button" aria-haspopup="true" aria-controls="dropdown-menu3">
|
||||||
|
<span><i class="fa-solid fa-file-export"></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">
|
||||||
|
<a href="Export?id=${page.id}&type=latex" class="dropdown-item">Latex</a>
|
||||||
|
<a href="Export?id=${page.id}&type=markdown" class="dropdown-item">Markdown</a>
|
||||||
|
<a href="Export?id=${page.id}&type=html" class="dropdown-item">HTLM</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown is-right is-inline-flex is-hoverable">
|
||||||
<div class="dropdown-trigger">
|
<div class="dropdown-trigger">
|
||||||
<button class="button" aria-haspopup="true" aria-controls="dropdown-menu3">
|
<button class="button" aria-haspopup="true" aria-controls="dropdown-menu3">
|
||||||
<span><i class="fa-solid fa-share-nodes"></i></span>
|
<span><i class="fa-solid fa-share-nodes"></i></span>
|
||||||
|
|
@ -45,6 +63,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="block" id="md">
|
<div class="block" id="md">
|
||||||
<c:forEach var="bloc" items="${page.listeBlocs}">
|
<c:forEach var="bloc" items="${page.listeBlocs}">
|
||||||
<div class="field is-grouped is-align-items-flex-start bloc-container" draggable="true">
|
<div class="field is-grouped is-align-items-flex-start bloc-container" draggable="true">
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -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 l’ordre');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,58 @@
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -19,7 +60,7 @@ export function initSocketBloc() {
|
||||||
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);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,11 +37,22 @@
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.is-list::before {
|
.is-list ul,
|
||||||
content: "• ";
|
.is-list ol {
|
||||||
|
list-style-position: inside;
|
||||||
|
margin-left: 1em;
|
||||||
|
padding-left: 1em;
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.is-list ul {
|
||||||
|
list-style-type: disc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-list ol {
|
||||||
|
list-style-type: decimal;
|
||||||
|
}
|
||||||
|
|
||||||
.is-separator {
|
.is-separator {
|
||||||
border-top: 1px solid #9a9996;
|
border-top: 1px solid #9a9996;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue