Spaces:
Sleeping
Sleeping
| const API = "/api"; | |
| let nbId = null, secId = null, pageId = null; | |
| let tool = "pen", paths = [], drawing = false; | |
| let canvas, ctx; | |
| window.onload = function() { | |
| canvas = document.getElementById("drawing-canvas"); | |
| ctx = canvas.getContext("2d"); | |
| resizeCanvas(); | |
| initDrawing(); | |
| loadNotebooks(); | |
| setInterval(savePage, 30000); | |
| }; | |
| function showRibbonTab(tab) { | |
| document.querySelectorAll(".tab-btn").forEach(b => b.classList.remove("active")); | |
| document.querySelectorAll(".ribbon-content").forEach(c => c.classList.remove("active")); | |
| document.querySelector('[data-tab="'+tab+'"]').classList.add("active"); | |
| document.getElementById(tab+"-tab").classList.add("active"); | |
| } | |
| function showEditor(type) { | |
| document.querySelectorAll(".toggle-btn").forEach(b => b.classList.remove("active")); | |
| document.querySelectorAll(".editor-panel").forEach(p => p.classList.remove("active")); | |
| document.getElementById("btn-"+type).classList.add("active"); | |
| document.getElementById(type+"-editor").classList.add("active"); | |
| if(type === "draw") setTimeout(resizeCanvas, 100); | |
| } | |
| function formatText(cmd, val) { document.execCommand(cmd, false, val); } | |
| // Insert Table | |
| function insertTable() { | |
| let html = '<table border="1" style="border-collapse:collapse;width:100%"><tr>'; | |
| for(let i=0; i<3; i++) html += '<td style="padding:8px;border:1px solid #ccc"> </td>'; | |
| html += '</tr></table><br>'; | |
| document.execCommand("insertHTML", false, html); | |
| } | |
| // Insert Link | |
| function insertLink() { | |
| let url = prompt("URL du lien:"); | |
| if(url) document.execCommand("insertHTML", false, '<a href="'+url+'" style="color:#7719AA">'+url+'</a>'); | |
| } | |
| // Handle Image | |
| function handleImage(input) { | |
| if(input.files[0]) { | |
| let reader = new FileReader(); | |
| reader.onload = e => document.execCommand("insertHTML", false, '<img src="'+e.target.result+'" style="max-width:100%">'); | |
| reader.readAsDataURL(input.files[0]); | |
| } | |
| } | |
| // Drawing | |
| function setDrawTool(t) { | |
| tool = t; | |
| document.querySelectorAll(".tool-btn").forEach(b => b.classList.remove("active")); | |
| document.getElementById("tool-"+t).classList.add("active"); | |
| } | |
| function initDrawing() { | |
| canvas.onpointerdown = e => { e.preventDefault(); drawing = true; startDraw(e); }; | |
| canvas.onpointermove = e => { if(drawing) moveDraw(e); }; | |
| canvas.onpointerup = () => { drawing = false; savePage(); }; | |
| canvas.ontouchstart = e => { e.preventDefault(); drawing = true; startDraw(e.touches[0]); }; | |
| canvas.ontouchmove = e => { e.preventDefault(); if(drawing) moveDraw(e.touches[0]); }; | |
| canvas.ontouchend = () => { drawing = false; }; | |
| canvas.onmousedown = e => { drawing = true; startDraw(e); }; | |
| canvas.onmousemove = e => { if(drawing) moveDraw(e); }; | |
| canvas.onmouseup = () => { drawing = false; }; | |
| } | |
| function resizeCanvas() { | |
| canvas.width = 2000; | |
| canvas.height = 2000; | |
| redraw(); | |
| } | |
| function getPos(e) { | |
| let rect = canvas.getBoundingClientRect(); | |
| return { x: e.clientX - rect.left, y: e.clientY - rect.top, p: e.pressure || 0.5 }; | |
| } | |
| function startDraw(e) { | |
| let pos = getPos(e); | |
| if(tool === "eraser") erase(pos); | |
| else { | |
| let color = document.getElementById("pen-color").value; | |
| let w = parseInt(document.getElementById("pen-width").value); | |
| paths.push({ points:[pos], color:tool==="highlighter"?rgba(color,.3):color, width:tool==="highlighter"?w*3:w }); | |
| redraw(); | |
| } | |
| } | |
| function moveDraw(e) { | |
| let pos = getPos(e); | |
| if(tool === "eraser") erase(pos); | |
| else if(paths.length) { | |
| paths[paths.length-1].points.push(pos); | |
| redraw(); | |
| } | |
| } | |
| function erase(pos) { | |
| paths = paths.filter(p => !p.points.some(pt => dist(pt,pos)<20)); | |
| redraw(); | |
| } | |
| function dist(a,b) { return Math.sqrt((a.x-b.x)**2+(a.y-b.y)**2); } | |
| function rgba(hex,a) { | |
| let r=parseInt(hex.slice(1,3),16),g=parseInt(hex.slice(3,5),16),b=parseInt(hex.slice(5,7),16); | |
| return `rgba(${r},${g},${b},${a})`; | |
| } | |
| function redraw() { | |
| ctx.clearRect(0,0,canvas.width,canvas.height); | |
| paths.forEach(p => { | |
| if(p.points.length<2) return; | |
| ctx.beginPath(); ctx.lineCap="round"; ctx.strokeStyle=p.color; ctx.lineWidth=p.width; | |
| ctx.moveTo(p.points[0].x,p.points[0].y); | |
| p.points.forEach(pt => ctx.lineTo(pt.x,pt.y)); | |
| ctx.stroke(); | |
| }); | |
| } | |
| function clearDrawing() { if(confirm("Effacer?")){ paths=[]; redraw(); savePage(); }} | |
| // Zoom | |
| function changeZoom(d) { | |
| let z = document.getElementById("zoom-level"); | |
| let v = parseInt(z.textContent)+d; | |
| z.textContent = Math.max(50,Math.min(200,v))+"%"; | |
| } | |
| // API | |
| async function loadNotebooks() { | |
| let res = await fetch(API+"/notebooks"); | |
| let list = document.getElementById("notebook-list"); | |
| list.innerHTML = ""; | |
| (await res.json()).forEach(nb => { | |
| let li = document.createElement("li"); | |
| li.textContent = nb.name; | |
| li.style.borderLeft = "3px solid "+nb.color; | |
| li.onclick = () => selectNotebook(nb.id, li); | |
| list.appendChild(li); | |
| }); | |
| } | |
| async function selectNotebook(id, el) { | |
| nbId = id; | |
| document.querySelectorAll("#notebook-list li").forEach(l => l.classList.remove("selected")); | |
| el.classList.add("selected"); | |
| let res = await fetch(API+"/notebooks/"+id+"/sections"); | |
| let list = document.getElementById("section-list"); | |
| list.innerHTML = ""; | |
| (await res.json()).forEach(s => { | |
| let li = document.createElement("li"); | |
| li.textContent = s.name; | |
| li.onclick = () => selectSection(s.id, li); | |
| list.appendChild(li); | |
| }); | |
| } | |
| async function selectSection(id, el) { | |
| secId = id; | |
| document.querySelectorAll("#section-list li").forEach(l => l.classList.remove("selected")); | |
| el.classList.add("selected"); | |
| let res = await fetch(API+"/sections/"+id+"/pages"); | |
| let list = document.getElementById("page-list"); | |
| list.innerHTML = ""; | |
| (await res.json()).forEach(p => { | |
| let li = document.createElement("li"); | |
| li.textContent = p.title; | |
| li.onclick = () => loadPage(p.id, li); | |
| list.appendChild(li); | |
| }); | |
| } | |
| async function loadPage(id, el) { | |
| pageId = id; | |
| document.querySelectorAll("#page-list li").forEach(l => l.classList.remove("selected")); | |
| el.classList.add("selected"); | |
| let p = await fetch(API+"/pages/"+id).then(r=>r.json()); | |
| document.getElementById("page-title").value = p.title; | |
| document.getElementById("rich-editor").innerHTML = p.content || ""; | |
| try { paths = JSON.parse(p.drawing_data||"[]"); } catch(e){ paths=[]; } | |
| redraw(); | |
| } | |
| async function savePage() { | |
| if(!pageId) return; | |
| await fetch(API+"/pages/"+pageId, { | |
| method:"PUT", headers:{"Content-Type":"application/json"}, | |
| body:JSON.stringify({ | |
| title:document.getElementById("page-title").value, | |
| content:document.getElementById("rich-editor").innerHTML, | |
| drawing_data:JSON.stringify(paths) | |
| }) | |
| }); | |
| document.getElementById("status-text").textContent = "Sauvegardé"; | |
| } | |
| async function addNotebook() { | |
| let name = prompt("Nom:"); | |
| if(name) { await fetch(API+"/notebooks", {method:"POST", headers:{"Content-Type":"application/json"}, body:JSON.stringify({name,color:"#7719AA"})}); loadNotebooks(); } | |
| } | |
| async function addSection() { | |
| if(!nbId) return alert("Sélectionnez un notebook"); | |
| let name = prompt("Nom:"); | |
| if(name) { await fetch(API+"/notebooks/"+nbId+"/sections", {method:"POST", headers:{"Content-Type":"application/json"}, body:JSON.stringify({name})}); selectNotebook(nbId, document.querySelector("#notebook-list li.selected")); } | |
| } | |
| async function addPage() { | |
| if(!secId) return alert("Sélectionnez une section"); | |
| let title = prompt("Titre:"); | |
| if(title) { await fetch(API+"/sections/"+secId+"/pages", {method:"POST", headers:{"Content-Type":"application/json"}, body:JSON.stringify({title})}); selectSection(secId, document.querySelector("#section-list li.selected")); } | |
| } | |