import { useState, useRef, useEffect, useMemo } from "react"; import * as sync from "./syncEngine.js"; const uid = () => Math.random().toString(36).slice(2, 10); const now = () => new Date().toISOString(); const fmtDate = (d) => new Date(d).toLocaleDateString("en-US",{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"}); const COLORS = ["#0d1117","#e94560","#0f3460","#533483","#e07c24","#21bf73","#fd5e53","#3282b8","#ff6b6b","#4ecdc4","#f7dc6f","#bb8fce","#85c1e9","#f8c471","#82e0aa","#f1948a","#aeb6bf","#ffffff","#f39c12","#000000"]; const PEN_SIZES = [1,2,3,5,8,12,20]; const COVERS = [ {bg:"linear-gradient(135deg,#0f0c29,#302b63,#24243e)",spine:"#1a1540",accent:"#6c63ff"}, {bg:"linear-gradient(135deg,#1a1a2e,#16213e,#0f3460)",spine:"#0a1628",accent:"#4fc3f7"}, {bg:"linear-gradient(135deg,#134e5e,#71b280)",spine:"#0e3d4a",accent:"#a5d6a7"}, {bg:"linear-gradient(135deg,#8e2de2,#4a00e0)",spine:"#3a0099",accent:"#ce93d8"}, {bg:"linear-gradient(135deg,#fc4a1a,#f7b733)",spine:"#c43d15",accent:"#ffe082"}, {bg:"linear-gradient(135deg,#c94b4b,#4b134f)",spine:"#3d0f40",accent:"#f48fb1"}, {bg:"linear-gradient(135deg,#11998e,#38ef7d)",spine:"#0d7a71",accent:"#b9f6ca"}, {bg:"linear-gradient(135deg,#373b44,#4286f4)",spine:"#2a2e35",accent:"#90caf9"}, {bg:"linear-gradient(135deg,#141e30,#243b55)",spine:"#0e1520",accent:"#78909c"}, {bg:"linear-gradient(135deg,#f12711,#f5af19)",spine:"#c01f0d",accent:"#fff59d"}, {bg:"linear-gradient(135deg,#2c3e50,#3498db)",spine:"#1e2b38",accent:"#81d4fa"}, {bg:"linear-gradient(135deg,#0f2027,#203a43,#2c5364)",spine:"#0a171c",accent:"#80cbc4"}, {bg:"linear-gradient(135deg,#aa4b6b,#6b6b83,#3b8d99)",spine:"#7a3550",accent:"#f48fb1"}, {bg:"linear-gradient(135deg,#1f1c2c,#928dab)",spine:"#15131f",accent:"#b39ddb"}, {bg:"linear-gradient(135deg,#654ea3,#eaafc8)",spine:"#4e3c7e",accent:"#f8bbd0"}, ]; const DEFAULT_FOLDERS = [{id:"all",name:"All Notes",icon:"๐Ÿ“‹"},{id:"starred",name:"Starred",icon:"โญ"},{id:"trash",name:"Trash",icon:"๐Ÿ—‘๏ธ"}]; const I = { plus:, search:, bold:, italic:, underline:, list:, listOrd:123, check:, trash:, star:, starFill:, pen:, eraser:, highlight:, undo:, redo:, sun:, moon:, menu:, text:, draw:, edit:, move:, restore:, grip:, logout:, user:, x:, save:, }; /* โ•โ•โ•โ•โ•โ•โ• Shared Components โ•โ•โ•โ•โ•โ•โ• */ function ConfirmDialog({title,message,confirmLabel="Delete",onConfirm,onCancel,dark}){ const T=useMemo(()=>({bg:dark?"#161b22":"#fff",border:dark?"#30363d":"#d0d7de",text:dark?"#e6edf3":"#1f2328",muted:dark?"#8b949e":"#656d76",danger:dark?"#f85149":"#cf222e"}),[dark]); return(
e.stopPropagation()}>
{title}
{message}
); } function ContextMenu({x,y,items,onClose,dark}){ const T=useMemo(()=>({bg:dark?"#1c2128":"#fff",border:dark?"#30363d":"#d0d7de",text:dark?"#e6edf3":"#1f2328",hover:dark?"#262c36":"#f3f4f6",danger:dark?"#f85149":"#cf222e"}),[dark]); useEffect(()=>{const h=()=>onClose();window.addEventListener("click",h);window.addEventListener("contextmenu",h);return()=>{window.removeEventListener("click",h);window.removeEventListener("contextmenu",h);};},[onClose]); return(
e.stopPropagation()}> {items.map((item,i)=>item.divider?
:item.submenu?:( ))}
); } function SubMenu({item,T,onClose}){ const [open,setOpen]=useState(false); return(
setOpen(true)} onMouseLeave={()=>setOpen(false)}>
{item.icon&&{item.icon}}{item.label}โ–ธ
{open&&
{item.submenu.map((sub,j)=>)}
}
); } function EditNotebookModal({notebook,onClose,onSave,dark}){ const [name,setName]=useState(notebook.name);const [sel,setSel]=useState(notebook.cover); const T=useMemo(()=>({bg:dark?"#161b22":"#fff",border:dark?"#30363d":"#d0d7de",text:dark?"#e6edf3":"#1f2328",muted:dark?"#8b949e":"#656d76",active:dark?"#388bfd":"#0969da"}),[dark]); return(
e.stopPropagation()}>

Edit Notebook

setName(e.target.value)} autoFocus style={{width:"100%",padding:"10px 14px",borderRadius:10,border:`1px solid ${T.border}`,background:dark?"#0d1117":"#f6f8fa",color:T.text,fontSize:14,outline:"none",marginBottom:16,boxSizing:"border-box"}} onKeyDown={e=>{if(e.key==="Enter"&&name.trim())onSave({name:name.trim(),cover:sel});}}/>
Cover style
{COVERS.map((c,i)=>)}
); } function CreateNotebookModal({onClose,onCreate,dark}){ const [name,setName]=useState("");const [sel,setSel]=useState(0); const T=useMemo(()=>({bg:dark?"#161b22":"#fff",border:dark?"#30363d":"#d0d7de",text:dark?"#e6edf3":"#1f2328",muted:dark?"#8b949e":"#656d76",active:dark?"#388bfd":"#0969da"}),[dark]); return(
e.stopPropagation()}>

New Notebook

setName(e.target.value)} placeholder="Notebook name..." autoFocus style={{width:"100%",padding:"10px 14px",borderRadius:10,border:`1px solid ${T.border}`,background:dark?"#0d1117":"#f6f8fa",color:T.text,fontSize:14,outline:"none",marginBottom:16,boxSizing:"border-box"}} onKeyDown={e=>{if(e.key==="Enter"&&name.trim())onCreate({name:name.trim(),cover:sel});}}/>
Choose cover style
{COVERS.map((c,i)=>)}
); } /* โ•โ•โ•โ•โ•โ•โ• ACCOUNT MENU โ•โ•โ•โ•โ•โ•โ• */ function AccountMenu({user,onClose,onLogout,onUserUpdate,dark}){ const [tab,setTab]=useState("profile"); // profile | email | password const [displayName,setDisplayName]=useState(user?.displayName||""); const [newEmail,setNewEmail]=useState(user?.email||""); const [emailPass,setEmailPass]=useState(""); const [curPass,setCurPass]=useState("");const [newPass,setNewPass]=useState("");const [newPass2,setNewPass2]=useState(""); const [msg,setMsg]=useState("");const [err,setErr]=useState("");const [saving,setSaving]=useState(false); const T=useMemo(()=>({bg:dark?"#161b22":"#fff",border:dark?"#30363d":"#d0d7de",text:dark?"#e6edf3":"#1f2328",muted:dark?"#8b949e":"#656d76",accent:dark?"#388bfd":"#0969da",danger:dark?"#f85149":"#cf222e",success:dark?"#3fb950":"#1a7f37",inputBg:dark?"#0d1117":"#f6f8fa"}),[dark]); const inp={width:"100%",padding:"10px 14px",borderRadius:10,border:`1px solid ${T.border}`,background:T.inputBg,color:T.text,fontSize:14,outline:"none",boxSizing:"border-box",marginBottom:10,fontFamily:"'DM Sans',sans-serif"}; const tbtn=(active)=>({padding:"7px 14px",borderRadius:8,border:"none",cursor:"pointer",fontSize:12,fontWeight:600,background:active?T.accent+"22":"transparent",color:active?T.accent:T.muted}); const clear=()=>{setMsg("");setErr("");}; const saveProfile=async()=>{if(!displayName.trim())return;clear();setSaving(true); try{const u=await sync.updateProfile(displayName.trim());onUserUpdate(u);setMsg("Display name updated");}catch(e){setErr(e.message);}setSaving(false);}; const saveEmail=async()=>{if(!newEmail||!emailPass)return;clear();setSaving(true); try{const u=await sync.updateEmail(newEmail,emailPass);onUserUpdate(u);setEmailPass("");setMsg("Email updated");}catch(e){setErr(e.message);}setSaving(false);}; const savePassword=async()=>{if(!curPass||!newPass)return;if(newPass!==newPass2){setErr("Passwords don't match");return;}clear();setSaving(true); try{await sync.updatePassword(curPass,newPass);setCurPass("");setNewPass("");setNewPass2("");setMsg("Password changed");}catch(e){setErr(e.message);}setSaving(false);}; return(
e.stopPropagation()}>

Account

{msg&&
{msg}
} {err&&
{err}
} {tab==="profile"&&<> setDisplayName(e.target.value)} style={inp} onKeyDown={e=>{if(e.key==="Enter")saveProfile();}}/> } {tab==="email"&&<> setNewEmail(e.target.value)} style={inp}/> setEmailPass(e.target.value)} style={inp} onKeyDown={e=>{if(e.key==="Enter")saveEmail();}}/> } {tab==="password"&&<> setCurPass(e.target.value)} style={inp}/> setNewPass(e.target.value)} style={inp}/> setNewPass2(e.target.value)} style={inp} onKeyDown={e=>{if(e.key==="Enter")savePassword();}}/> }
); } /* โ•โ•โ•โ•โ•โ•โ• AUTH SCREEN โ•โ•โ•โ•โ•โ•โ• */ function AuthScreen({onLogin,dark}){ const [isLogin,setIsLogin]=useState(true); const [email,setEmail]=useState("");const [pass,setPass]=useState("");const [name,setName]=useState(""); const [err,setErr]=useState("");const [loading,setLoading]=useState(false); const T=useMemo(()=>({bg:dark?"#0d1117":"#f6f8fa",card:dark?"#161b22":"#fff",border:dark?"#30363d":"#d0d7de",text:dark?"#e6edf3":"#1f2328",muted:dark?"#8b949e":"#656d76",accent:dark?"#388bfd":"#0969da",danger:dark?"#f85149":"#cf222e"}),[dark]); const submit=async()=>{ if(!email||!pass){setErr("Email and password required");return;} setLoading(true);setErr(""); try{ const user=isLogin?await sync.login(email,pass):await sync.register(email,pass,name||undefined); onLogin(user); }catch(e){setErr(e.message==="AUTH"?"Invalid credentials":e.message);} finally{setLoading(false);} }; return(
โœ๏ธ
InkNote
{isLogin?"Sign in to sync your notes":"Create your account"}
{!isLogin&&setName(e.target.value)} placeholder="Display name" style={{width:"100%",padding:"12px 16px",borderRadius:12,border:`1px solid ${T.border}`,background:T.bg,color:T.text,fontSize:14,outline:"none",marginBottom:12,boxSizing:"border-box"}}/>} setEmail(e.target.value)} placeholder="Email" autoFocus style={{width:"100%",padding:"12px 16px",borderRadius:12,border:`1px solid ${T.border}`,background:T.bg,color:T.text,fontSize:14,outline:"none",marginBottom:12,boxSizing:"border-box"}}/> setPass(e.target.value)} placeholder="Password" style={{width:"100%",padding:"12px 16px",borderRadius:12,border:`1px solid ${T.border}`,background:T.bg,color:T.text,fontSize:14,outline:"none",marginBottom:16,boxSizing:"border-box"}} onKeyDown={e=>{if(e.key==="Enter")submit();}}/> {err&&
{err}
}
{isLogin?"Don't have an account? ":"Already have an account? "}
); } /* โ•โ•โ•โ•โ•โ•โ• TASK LIST (proper React state, not contentEditable) โ•โ•โ•โ•โ•โ•โ• */ function TaskList({tasks,onChange,dark}){ const T=useMemo(()=>({border:dark?"#30363d":"#d0d7de",text:dark?"#e6edf3":"#1f2328",muted:dark?"#8b949e":"#656d76",active:dark?"#388bfd":"#0969da",faint:dark?"#484f58":"#afb8c1",done:dark?"#484f58":"#afb8c1"}),[dark]); const inputRefs=useRef({}); const toggle=(i)=>{const t=[...tasks];t[i]={...t[i],done:!t[i].done};onChange(t);}; const setText=(i,text)=>{const t=[...tasks];t[i]={...t[i],text};onChange(t);}; const addAfter=(i)=>{const t=[...tasks];const nid=uid();t.splice(i+1,0,{id:nid,text:"",done:false});onChange(t);setTimeout(()=>inputRefs.current[nid]?.focus(),0);}; const remove=(i)=>{ if(tasks.length<=1&&tasks[0].text==="")return; const t=[...tasks];t.splice(i,1);onChange(t); if(t.length>0){const fi=Math.max(0,i-1);setTimeout(()=>{const el=inputRefs.current[t[fi]?.id];if(el){el.focus();el.selectionStart=el.value.length;}},0);} }; const handleKey=(e,i)=>{ if(e.key==="Enter"){e.preventDefault();if(tasks[i].text===""){remove(i);}else{addAfter(i);}} else if(e.key==="Backspace"&&tasks[i].text===""&&tasks.length>1){e.preventDefault();remove(i);} }; if(!tasks||tasks.length===0)return null; return(
Tasks ({tasks.filter(t=>t.done).length}/{tasks.length})
{tasks.map((task,i)=>(
toggle(i)} style={{width:16,height:16,accentColor:T.active,cursor:"pointer",flexShrink:0}}/> inputRefs.current[task.id]=el} type="text" value={task.text} onChange={e=>setText(i,e.target.value)} onKeyDown={e=>handleKey(e,i)} placeholder="Task..." style={{flex:1,border:"none",background:"transparent",outline:"none",fontSize:14,color:task.done?T.done:T.text,textDecoration:task.done?"line-through":"none",fontFamily:"'DM Sans',sans-serif"}}/>
))}
); } /* โ•โ•โ•โ•โ•โ•โ• NOTE EDITOR โ•โ•โ•โ•โ•โ•โ• */ function NoteEditor({note,onUpdate,dark}){ const [mode,setMode]=useState("text"); const editorRef=useRef(null);const canvasRef=useRef(null);const ctxRef=useRef(null); const [isDrawing,setIsDrawing]=useState(false); const [tool,setTool]=useState("pen");const [penColor,setPenColor]=useState(dark?"#e6edf3":"#1a1a2e");const [penSize,setPenSize]=useState(3); const [undoStack,setUndoStack]=useState([]);const [redoStack,setRedoStack]=useState([]); const [showColors,setShowColors]=useState(false);const [showSizes,setShowSizes]=useState(false); const [selectedImg,setSelectedImg]=useState(null); const [hideDrawing,setHideDrawing]=useState(false); const [drawingBlend,setDrawingBlend]=useState({mode:"normal",opacity:1}); const [isMobileViewport,setIsMobileViewport]=useState(window.innerWidth<768); const stageRef=useRef(null); const saveTimerRef=useRef(null); const dirtyRef=useRef(false); const CW=1200,CH=1600; // Canonical drawing resolution shared by all devices. const SAVE_DEBOUNCE_MS=600; const [stageSize,setStageSize]=useState({w:CW,h:CH}); const parsed=useMemo(()=>{try{const p=JSON.parse(note.content);return{html:p.html||"",tasks:p.tasks||[]};}catch{return{html:note.content||"",tasks:[]};}},[note.content,note.id]); const T=useMemo(()=>({bg:dark?"#0d1117":"#ffffff",toolbar:dark?"#161b22":"#f6f8fa",border:dark?"#30363d":"#d0d7de",text:dark?"#e6edf3":"#1f2328",muted:dark?"#8b949e":"#656d76",active:dark?"#388bfd":"#0969da",faint:dark?"#484f58":"#afb8c1"}),[dark]); useEffect(()=>{ const el=editorRef.current; if(!el)return; const prevNoteId=el.dataset.noteId; const isNewNote=prevNoteId!==note.id; if(isNewNote){ el.innerHTML=parsed.html; el.dataset.noteId=note.id; return; } // Late content arrival for the same note (mobile/network): fill only if editor is still empty. const hasLocalText=(el.textContent||"").trim().length>0; if(!hasLocalText && parsed.html){ el.innerHTML=parsed.html; } },[note.id,parsed.html]); useEffect(()=>{ setHideDrawing(false); const isLegacyRaster=typeof note.drawingData==="string"&&/^data:image\/(jpeg|jpg|webp)/i.test(note.drawingData); if(!note.drawingData){setDrawingBlend({mode:"normal",opacity:1});return;} let cancelled=false; const img=new Image(); img.onload=()=>{ if(cancelled)return; try{ const c=document.createElement("canvas");c.width=32;c.height=32; const cx=c.getContext("2d",{willReadFrequently:true});if(!cx)throw new Error("ctx"); cx.drawImage(img,0,0,32,32); const data=cx.getImageData(0,0,32,32).data; let solid=0,black=0; for(let i=0;i0.9)setDrawingBlend({mode:"screen",opacity:0.62}); else if(isLegacyRaster)setDrawingBlend({mode:"screen",opacity:0.42}); else setDrawingBlend({mode:"normal",opacity:1}); }catch{ setDrawingBlend(isLegacyRaster?{mode:"screen",opacity:0.42}:{mode:"normal",opacity:1}); } }; img.onerror=()=>{if(!cancelled)setHideDrawing(true);}; img.src=note.drawingData; return()=>{cancelled=true;}; },[note.id,note.drawingData]); // Keep one fixed-aspect drawing stage regardless of device dimensions. useEffect(()=>{ const fitStage=()=>{ const host=stageRef.current; if(!host)return; const W=Math.max(1,host.clientWidth); const mobile=window.innerWidth<768; setIsMobileViewport(mobile); const sidePad=mobile?10:28; const desktopMax=760; const minW=mobile?220:320; const maxAllowed=Math.max(220,W-sidePad*2); const targetW=mobile?maxAllowed:Math.min(desktopMax,maxAllowed); const w=Math.floor(Math.max(minW,targetW)); const h=Math.floor((w*CH)/CW); setStageSize({w,h}); }; fitStage(); if(typeof ResizeObserver!=="undefined"){ const ro=new ResizeObserver(fitStage); if(stageRef.current)ro.observe(stageRef.current); window.addEventListener("resize",fitStage); return()=>{ro.disconnect();window.removeEventListener("resize",fitStage);}; } window.addEventListener("resize",fitStage); return()=>window.removeEventListener("resize",fitStage); },[]); const saveDrawingNow=()=>{ if(!canvasRef.current||!dirtyRef.current)return; onUpdate({drawingData:canvasRef.current.toDataURL("image/png")}); dirtyRef.current=false; }; const scheduleDrawingSave=()=>{ if(saveTimerRef.current)clearTimeout(saveTimerRef.current); saveTimerRef.current=setTimeout(()=>{saveTimerRef.current=null;saveDrawingNow();},SAVE_DEBOUNCE_MS); }; /* โ”€โ”€ DRAWING: init canvas once on mode switch โ”€โ”€ */ useEffect(()=>{ if(mode!=="draw")return; const c=canvasRef.current;if(!c)return; c.width=CW;c.height=CH; const ctx=c.getContext("2d");ctx.lineCap="round";ctx.lineJoin="round";ctxRef.current=ctx; ctx.clearRect(0,0,CW,CH); dirtyRef.current=false; if(note.drawingData){ const img=new Image();img.onload=()=>{ const t=document.createElement("canvas");t.width=CW;t.height=CH; const tctx=t.getContext("2d",{willReadFrequently:true});if(!tctx){ctx.drawImage(img,0,0,CW,CH);return;} tctx.drawImage(img,0,0,CW,CH); try{ const sc=document.createElement("canvas");sc.width=40;sc.height=40; const sctx=sc.getContext("2d",{willReadFrequently:true});if(!sctx)throw new Error("ctx"); sctx.drawImage(t,0,0,40,40); const sample=sctx.getImageData(0,0,40,40).data; let solid=0,black=0; for(let i=0;i0.9){ const im=tctx.getImageData(0,0,CW,CH);const d=im.data; for(let i=0;i245)d[i+3]=0; } tctx.putImageData(im,0,0); } }catch{} ctx.drawImage(t,0,0,CW,CH); };img.src=note.drawingData; } },[mode]); /* Save drawing on mode switch and debounced stroke-end updates. */ const switchMode=(m)=>{ if(mode==="draw"&&m==="text"&&dirtyRef.current&&canvasRef.current){ if(saveTimerRef.current){clearTimeout(saveTimerRef.current);saveTimerRef.current=null;} saveDrawingNow(); } setMode(m);setShowColors(false);setShowSizes(false);setSelectedImg(null); }; const pushUndo=()=>{if(!canvasRef.current)return;const d=canvasRef.current.toDataURL("image/png");setUndoStack(p=>[...p.slice(-15),d]);setRedoStack([]);}; const undo=()=>{if(!undoStack.length)return;const s=undoStack[undoStack.length-1];setUndoStack(p=>p.slice(0,-1)); setRedoStack(p=>[...p,canvasRef.current.toDataURL("image/png")]); const i=new Image();i.onload=()=>{ctxRef.current.clearRect(0,0,CW,CH);ctxRef.current.drawImage(i,0,0,CW,CH);dirtyRef.current=true;scheduleDrawingSave();};i.src=s;}; const redo=()=>{if(!redoStack.length)return;const s=redoStack[redoStack.length-1];setRedoStack(p=>p.slice(0,-1)); setUndoStack(p=>[...p,canvasRef.current.toDataURL("image/png")]); const i=new Image();i.onload=()=>{ctxRef.current.clearRect(0,0,CW,CH);ctxRef.current.drawImage(i,0,0,CW,CH);dirtyRef.current=true;scheduleDrawingSave();};i.src=s;}; const clearCanvas=()=>{pushUndo();ctxRef.current?.clearRect(0,0,CW,CH);dirtyRef.current=true;scheduleDrawingSave();}; const getPos=(e)=>{const c=canvasRef.current,r=c.getBoundingClientRect();const t=e.touches?e.touches[0]:e;return{x:(t.clientX-r.left)*(CW/r.width),y:(t.clientY-r.top)*(CH/r.height)};}; const startDraw=(e)=>{e.preventDefault();pushUndo();const p=getPos(e);setIsDrawing(true); const ctx=ctxRef.current;ctx.beginPath();ctx.moveTo(p.x,p.y); if(tool==="eraser"){ctx.globalCompositeOperation="destination-out";ctx.lineWidth=penSize*5;} else if(tool==="highlighter"){ctx.globalCompositeOperation="source-over";ctx.strokeStyle=penColor+"44";ctx.lineWidth=penSize*6;} else{ctx.globalCompositeOperation="source-over";ctx.strokeStyle=penColor;ctx.lineWidth=penSize;}}; const doDraw=(e)=>{if(!isDrawing)return;e.preventDefault();const p=getPos(e);ctxRef.current.lineTo(p.x,p.y);ctxRef.current.stroke();}; const stopDraw=(e)=>{if(!isDrawing)return;e?.preventDefault();setIsDrawing(false);ctxRef.current.closePath();ctxRef.current.globalCompositeOperation="source-over";dirtyRef.current=true;scheduleDrawingSave();}; useEffect(()=>()=>{if(saveTimerRef.current)clearTimeout(saveTimerRef.current);saveDrawingNow();},[]); /* โ”€โ”€ TEXT โ”€โ”€ */ const saveContent=(html,tasks)=>{onUpdate({content:JSON.stringify({html,tasks})});}; const onHtmlChange=()=>{if(editorRef.current)saveContent(editorRef.current.innerHTML,parsed.tasks);}; const onTasksChange=(tasks)=>{saveContent(parsed.html,tasks);}; const addTaskList=()=>{if(parsed.tasks.length===0)onTasksChange([{id:uid(),text:"",done:false}]);}; const exec=(cmd,val=null)=>{document.execCommand(cmd,false,val);editorRef.current?.focus();onHtmlChange();}; /* โ”€โ”€ IMAGE PASTE โ”€โ”€ */ useEffect(()=>{ const el=editorRef.current;if(!el||mode!=="text")return; const h=(e)=>{ const items=e.clipboardData?.items;if(!items)return; for(const item of items){ if(item.type.startsWith("image/")){ e.preventDefault(); const blob=item.getAsFile();if(!blob)return; const reader=new FileReader(); reader.onload=(ev)=>{ document.execCommand("insertHTML",false, ``); if(editorRef.current)saveContent(editorRef.current.innerHTML,parsed.tasks); }; reader.readAsDataURL(blob);return; } } }; el.addEventListener("paste",h);return()=>el.removeEventListener("paste",h); },[mode,parsed.tasks]); /* โ”€โ”€ IMAGE SELECT + DRAG RESIZE โ”€โ”€ */ useEffect(()=>{ const el=editorRef.current;if(!el||mode!=="text")return; const onClick=(e)=>{ const img=e.target.closest("img[data-rid]"); setSelectedImg(img||null); }; const onPointerDown=(e)=>{ const img=e.target.closest("img[data-rid]");if(!img)return; const rect=img.getBoundingClientRect(); const px=e.clientX||(e.touches?.[0]?.clientX); // Only start resize if clicking within 20px of right edge if(px{ ev.preventDefault(); const mx=ev.clientX||(ev.touches?.[0]?.clientX); const nw=Math.max(40,Math.min(cW,startW+(mx-startX))); const pct=Math.round(nw/cW*100); img.style.width=pct+"%";img.style.maxWidth=pct+"%"; }; const up=()=>{ document.removeEventListener("mousemove",move);document.removeEventListener("mouseup",up); document.removeEventListener("touchmove",move);document.removeEventListener("touchend",up); if(editorRef.current)saveContent(editorRef.current.innerHTML,parsed.tasks); }; document.addEventListener("mousemove",move);document.addEventListener("mouseup",up); document.addEventListener("touchmove",move,{passive:false});document.addEventListener("touchend",up); }; // Cursor hint near right edge const onMove=(e)=>{ const img=e.target.closest("img[data-rid]");if(!img){return;} const r=img.getBoundingClientRect(); img.style.cursor=e.clientX>r.right-20?"ew-resize":"pointer"; }; el.addEventListener("click",onClick); el.addEventListener("mousedown",onPointerDown); el.addEventListener("touchstart",onPointerDown,{passive:false}); el.addEventListener("mousemove",onMove); return()=>{el.removeEventListener("click",onClick);el.removeEventListener("mousedown",onPointerDown); el.removeEventListener("touchstart",onPointerDown);el.removeEventListener("mousemove",onMove);}; },[mode,parsed.tasks]); // Blue outline on selected image useEffect(()=>{ const el=editorRef.current;if(!el)return; el.querySelectorAll("img[data-rid]").forEach(img=>{ if(img===selectedImg){img.style.outline=`3px solid ${T.active}`;img.style.outlineOffset="2px";} else{img.style.outline="none";img.style.outlineOffset="0";} }); },[selectedImg,T.active]); // Deselect on outside click useEffect(()=>{if(!selectedImg)return; const h=(e)=>{if(!editorRef.current?.contains(e.target)&&!e.target.closest("[data-imgtb]"))setSelectedImg(null);}; document.addEventListener("pointerdown",h);return()=>document.removeEventListener("pointerdown",h); },[selectedImg]); const setImgSize=(p)=>{if(!selectedImg)return;selectedImg.style.width=p+"%";selectedImg.style.maxWidth=p+"%";if(editorRef.current)saveContent(editorRef.current.innerHTML,parsed.tasks);}; const deleteImg=()=>{if(!selectedImg)return;selectedImg.remove();setSelectedImg(null);if(editorRef.current)saveContent(editorRef.current.innerHTML,parsed.tasks);}; /* โ”€โ”€ RENDER โ”€โ”€ */ const tb=(a)=>({display:"flex",alignItems:"center",justifyContent:"center",width:30,height:30,borderRadius:6,border:"none",cursor:"pointer",background:a?T.active+"22":"transparent",color:a?T.active:T.text,transition:"all .12s",flexShrink:0}); const mb=(m,ic,lb)=>; return(
{/* Main toolbar */}
{mb("text",I.text,"Write")}{mb("draw",I.draw,"Draw")}
{mode==="text"?(<>
):(<>
{showColors&&
{COLORS.map(c=>
}
{showSizes&&
{PEN_SIZES.map(s=>)}
}
)}
{/* Image toolbar โ€” appears when an image is selected */} {selectedImg&&mode==="text"&&
Size: {[25,33,50,75,100].map(p=>)}
ยท drag right edge to resize
} {/* Content area */}
{note.drawingData&&!hideDrawing&&setHideDrawing(true)} style={{position:"absolute",left:0,top:0,width:"100%",height:stageSize.h,pointerEvents:"none",objectFit:"fill",zIndex:2,opacity:drawingBlend.opacity,mixBlendMode:drawingBlend.mode}}/>} {mode==="draw"&&} {mode==="text"&&}
); } /* โ•โ•โ•โ•โ•โ•โ• NOTEBOOK COVER โ•โ•โ•โ•โ•โ•โ• */ function NotebookCover({notebook,noteCount,onClick,onContextMenu,isActive,isDragOver,draggable,onDragStart,onDragEnd,onDragOver,onDrop}){ const cover=COVERS[notebook.cover%COVERS.length];const [hovered,setHovered]=useState(false); return(); } /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• MAIN APP (offline-first) โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */ export default function NoteApp(){ const [dark,setDark]=useState(true); const [user,setUser]=useState(null);const [loading,setLoading]=useState(true); const [sidebarOpen,setSidebarOpen]=useState(window.innerWidth>768); const [activeFolder,setActiveFolder]=useState("dashboard"); const [activeNote,setActiveNote]=useState(null); const [searchQuery,setSearchQuery]=useState(""); const [showNewNb,setShowNewNb]=useState(false); const [syncStatus,setSyncStatus]=useState("synced"); const [ctxMenu,setCtxMenu]=useState(null); const [confirmDialog,setConfirmDialog]=useState(null); const [editingNb,setEditingNb]=useState(null); const [showAccount,setShowAccount]=useState(false); const [dragNbId,setDragNbId]=useState(null);const [dragOverNbId,setDragOverNbId]=useState(null); const [notebooks,setNotebooks]=useState([]);const [notes,setNotes]=useState([]); const [noteContents,setNoteContents]=useState({}); const [isMobile,setIsMobile]=useState(window.innerWidth<768); const [mobileView,setMobileView]=useState("list"); // list | editor const [installPrompt,setInstallPrompt]=useState(null); const T=useMemo(()=>dark?{ bg:"#0d1117",sidebar:"#010409",sidebarActive:"#161b22",card:"#161b22",toolbar:"#161b22",border:"#30363d",borderLight:"#21262d",text:"#e6edf3",textSec:"#c9d1d9",muted:"#8b949e",faint:"#484f58",accent:"#388bfd",accentSoft:"#388bfd22",accentText:"#58a6ff",danger:"#f85149",success:"#3fb950",warning:"#d29922",inputBg:"#0d1117",dashBg:"#0d1117", }:{ bg:"#f6f8fa",sidebar:"#ffffff",sidebarActive:"#eef2ff",card:"#ffffff",toolbar:"#ffffff",border:"#d0d7de",borderLight:"#e5e7eb",text:"#1f2328",textSec:"#424a53",muted:"#656d76",faint:"#afb8c1",accent:"#0969da",accentSoft:"#0969da18",accentText:"#0550ae",danger:"#cf222e",success:"#1a7f37",warning:"#9a6700",inputBg:"#f6f8fa",dashBg:"#f0f2f5", },[dark]); // โ”€โ”€ Responsive โ”€โ”€ useEffect(()=>{const h=()=>{const m=window.innerWidth<768;setIsMobile(m);if(!m)setSidebarOpen(true);};window.addEventListener("resize",h);return()=>window.removeEventListener("resize",h);},[]); // โ”€โ”€ PWA install prompt โ”€โ”€ useEffect(()=>{ const h=(e)=>{e.preventDefault();setInstallPrompt(e);}; window.addEventListener("beforeinstallprompt",h); return()=>window.removeEventListener("beforeinstallprompt",h); },[]); const handleInstall=async()=>{if(!installPrompt)return;installPrompt.prompt();const r=await installPrompt.userChoice;if(r.outcome==="accepted")setInstallPrompt(null);}; // โ”€โ”€ Sync status listener โ”€โ”€ useEffect(()=>sync.onSyncStatus(setSyncStatus),[]); // โ”€โ”€ Data change listener โ€” reload from IndexedDB โ”€โ”€ useEffect(()=>sync.onDataChange(async()=>{ setNotebooks(await sync.getNotebooks()); setNotes(await sync.getNotes()); // Keep loaded note content cached to avoid editor flicker/stalls on periodic sync }),[]); // โ”€โ”€ Startup: restore session โ†’ load local data โ†’ sync โ”€โ”€ useEffect(()=>{(async()=>{ try{ const u=await sync.restoreSession(); if(u){setUser(u);await reloadLocal(); // Background sync if online try{await sync.fullSync();await reloadLocal();}catch(e){if(e.message==="AUTH"){setUser(null);}} } }catch{} setLoading(false); })();},[]); // Reload from IndexedDB const reloadLocal=async()=>{ setNotebooks(await sync.getNotebooks()); setNotes(await sync.getNotes()); }; const onLogin=async(u)=>{setUser(u); try{await sync.fullSync();}catch{} await reloadLocal(); }; const onLogout=async()=>{await sync.logout();setUser(null);setNotes([]);setNotebooks([]);setNoteContents({});setActiveNote(null);setActiveFolder("dashboard");}; const activeNotes=useMemo(()=>notes.filter(n=>!n.trashed),[notes]); const trashedNotes=useMemo(()=>notes.filter(n=>n.trashed),[notes]); const filteredNotes=useMemo(()=>{ let list; if(activeFolder==="trash")list=trashedNotes; else if(activeFolder==="starred")list=activeNotes.filter(n=>n.starred); else if(activeFolder==="all")list=activeNotes; else if(activeFolder!=="dashboard")list=activeNotes.filter(n=>n.folderId===activeFolder); else list=activeNotes; if(searchQuery){const q=searchQuery.toLowerCase();list=list.filter(n=>n.title.toLowerCase().includes(q));} return list.sort((a,b)=>new Date(b.updatedAt)-new Date(a.updatedAt)); },[notes,activeFolder,searchQuery,activeNotes,trashedNotes]); const currentNoteMeta=activeNote?notes.find(n=>n.id===activeNote):null; const currentNoteContent=activeNote?noteContents[activeNote]:null; const activeNoteHasContent=!!currentNoteContent; const currentNote=currentNoteMeta&¤tNoteContent?{...currentNoteMeta,...currentNoteContent}:null; const currentNoteSafe=currentNote||( currentNoteMeta ? {...currentNoteMeta,content:currentNoteContent?.content||"",drawingData:currentNoteContent?.drawingData||null} : null ); // Load note content on demand โ€” fetches server-first on cache miss (page load = all cache empty) useEffect(()=>{ if(!activeNote || activeNoteHasContent) return; let cancelled=false; sync.loadNoteContent(activeNote) .then(c=>{ if(!cancelled) setNoteContents(p=>({...p,[activeNote]:c})); }) .catch(()=>{ if(!cancelled) setNoteContents(p=>({...p,[activeNote]:{content:"",drawingData:null}})); }); return ()=>{cancelled=true;}; },[activeNote,activeNoteHasContent]); // Hard fallback: never stay in loading state forever. useEffect(()=>{ if(!activeNote || activeNoteHasContent) return; const t=setTimeout(()=>{ setNoteContents(p=>p[activeNote]?p:{...p,[activeNote]:{content:"",drawingData:null}}); },5000); return ()=>clearTimeout(t); },[activeNote,activeNoteHasContent]); // Keep currently open note content fresh across devices. useEffect(()=>{ if(!user||!activeNote)return; const i=setInterval(()=>{ if(!sync.isOnline())return; sync.loadNoteContent(activeNote,{forceRemote:true}).then(c=>{ setNoteContents(p=>{ const prev=p[activeNote]; if(prev&&prev.content===c.content&&prev.drawingData===c.drawingData)return p; return {...p,[activeNote]:c}; }); }).catch(()=>{}); },6000); return()=>clearInterval(i); },[user,activeNote]); // โ”€โ”€ CRUD โ€” all go through sync engine โ”€โ”€ const createNote=async()=>{ const fid=["all","starred","trash","dashboard"].includes(activeFolder)?(notebooks[0]?.id):activeFolder;if(!fid)return; const note=await sync.createNote(fid); setNotes(p=>[note,...p]);setNoteContents(p=>({...p,[note.id]:{content:"",drawingData:null}})); setActiveNote(note.id);if(activeFolder==="dashboard")setActiveFolder(fid); if(isMobile)setMobileView("editor"); }; const updateNote=async(id,u)=>{ if(u.title!==undefined){setNotes(p=>p.map(n=>n.id===id?{...n,title:u.title,updatedAt:now()}:n));sync.updateNoteTitle(id,u.title);} if(u.content!==undefined){setNoteContents(p=>({...p,[id]:{...p[id],content:u.content}}));sync.updateNoteContent(id,u.content);} if(u.drawingData!==undefined){setNoteContents(p=>({...p,[id]:{...p[id],drawingData:u.drawingData}}));sync.updateNoteDrawing(id,u.drawingData);} }; const toggleStar=async(id)=>{const nv=await sync.toggleNoteStar(id);setNotes(p=>p.map(n=>n.id===id?{...n,starred:nv}:n));}; const trashNoteAction=(id)=>{const note=notes.find(n=>n.id===id); setConfirmDialog({title:"Move to Trash",message:`Move "${note?.title}" to trash?`,confirmLabel:"Move to Trash", onConfirm:async()=>{await sync.trashNote(id);setNotes(p=>p.map(n=>n.id===id?{...n,trashed:true}:n)); if(activeNote===id){setActiveNote(null);if(isMobile)setMobileView("list");}setConfirmDialog(null);}}); }; const restoreNote=async(id)=>{await sync.restoreNoteLocal(id);setNotes(p=>p.map(n=>n.id===id?{...n,trashed:false}:n));if(activeNote===id){setActiveNote(null);if(isMobile)setMobileView("list");}}; const permanentDeleteNote=(id)=>{const note=notes.find(n=>n.id===id); setConfirmDialog({title:"Delete Permanently",message:`Permanently delete "${note?.title}"? This cannot be undone.`, onConfirm:async()=>{await sync.permanentDeleteNoteLocal(id);setNotes(p=>p.filter(n=>n.id!==id)); if(activeNote===id){setActiveNote(null);if(isMobile)setMobileView("list");}setConfirmDialog(null);}}); }; const emptyTrash=()=>{ setConfirmDialog({title:"Empty Trash",message:`Permanently delete all ${trashedNotes.length} trashed notes?`, onConfirm:async()=>{await sync.emptyTrashLocal();setNotes(p=>p.filter(n=>!n.trashed));setActiveNote(null);setConfirmDialog(null);}}); }; const moveNoteAction=async(id,newFolderId)=>{await sync.moveNote(id,newFolderId);setNotes(p=>p.map(n=>n.id===id?{...n,folderId:newFolderId}:n));}; const createNb=async({name,cover})=>{const nb=await sync.createNotebook({name,cover});setNotebooks(p=>[...p,nb]);setShowNewNb(false);setActiveFolder(nb.id);}; const updateNb=async(id,updates)=>{await sync.updateNotebook(id,updates);setNotebooks(p=>p.map(nb=>nb.id===id?{...nb,...updates}:nb));setEditingNb(null);}; const requestDeleteNb=(id)=>{const nb=notebooks.find(n=>n.id===id);const nc=activeNotes.filter(n=>n.folderId===id).length; setConfirmDialog({title:"Delete Notebook",message:`Delete "${nb?.name}"${nc>0?` and trash its ${nc} notes`:""} permanently?`, onConfirm:async()=>{await sync.deleteNotebookLocal(id); setNotes(p=>p.map(n=>n.folderId===id?{...n,trashed:true}:n));setNotebooks(p=>p.filter(n=>n.id!==id)); if(activeFolder===id)setActiveFolder("dashboard");setConfirmDialog(null);}}); }; const handleNbDragStart=(e,id)=>{setDragNbId(id);e.dataTransfer.effectAllowed="move";}; const handleNbDragOver=(e,id)=>{e.preventDefault();if(dragNbId&&dragNbId!==id)setDragOverNbId(id);}; const handleNbDrop=async(e,targetId)=>{e.preventDefault();if(!dragNbId||dragNbId===targetId)return; setNotebooks(prev=>{const a=[...prev];const fi=a.findIndex(n=>n.id===dragNbId);const ti=a.findIndex(n=>n.id===targetId);if(fi<0||ti<0)return prev;const[item]=a.splice(fi,1);a.splice(ti,0,item); const ids=a.map(n=>n.id);sync.reorderNotebooks(ids);return a.map((n,i)=>({...n,sortOrder:i}));}); setDragNbId(null);setDragOverNbId(null);}; const handleNbDragEnd=()=>{setDragNbId(null);setDragOverNbId(null);}; const handleNbContextMenu=(e,nb)=>{e.preventDefault();e.stopPropagation(); setCtxMenu({x:e.clientX,y:e.clientY,items:[{label:"Edit",icon:I.edit,action:()=>setEditingNb(nb)},{divider:true},{label:"Delete Notebook",icon:I.trash,danger:true,action:()=>requestDeleteNb(nb.id)}]});}; const handleNoteContextMenu=(e,note)=>{e.preventDefault();e.stopPropagation(); if(note.trashed){setCtxMenu({x:e.clientX,y:e.clientY,items:[{label:"Restore",icon:I.restore,action:()=>restoreNote(note.id)},{divider:true},{label:"Delete Permanently",icon:I.trash,danger:true,action:()=>permanentDeleteNote(note.id)}]});} else{const subs=notebooks.filter(nb=>nb.id!==note.folderId).map(nb=>({label:nb.name,colorDot:COVERS[nb.cover%COVERS.length].bg,action:()=>moveNoteAction(note.id,nb.id)})); setCtxMenu({x:e.clientX,y:e.clientY,items:[{label:note.starred?"Unstar":"Star",icon:note.starred?I.starFill:I.star,action:()=>toggleStar(note.id)},...(subs.length>0?[{label:"Move to...",icon:I.move,submenu:subs}]:[]),{divider:true},{label:"Move to Trash",icon:I.trash,danger:true,action:()=>trashNoteAction(note.id)}]});} }; useEffect(()=>{const l=document.createElement("link");l.href="https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,300;0,6..72,400;0,6..72,500;0,6..72,600;1,6..72,400&family=DM+Sans:wght@400;500;600;700&display=swap";l.rel="stylesheet";document.head.appendChild(l);},[]); // โ”€โ”€ Periodic sync (every 30s if online) โ”€โ”€ useEffect(()=>{const i=setInterval(()=>{if(sync.isOnline())sync.fullSync().then(()=>reloadLocal()).catch(()=>{});},30000);return()=>clearInterval(i);},[]); // โ”€โ”€ History API โ€” browser/phone back button โ”€โ”€ const navStateRef=useRef({folder:"dashboard",note:null,view:"list"}); useEffect(()=>{ const cur=navStateRef.current; if(cur.folder!==activeFolder||cur.note!==activeNote||cur.view!==mobileView){ const state={folder:activeFolder,note:activeNote,view:mobileView}; try{window.history.pushState(state,"");}catch{} navStateRef.current=state; } },[activeFolder,activeNote,mobileView]); useEffect(()=>{ const handler=(e)=>{ const s=e.state; if(s){setActiveFolder(s.folder||"dashboard");setActiveNote(s.note||null);if(isMobile)setMobileView(s.view||"list");} else{if(activeNote){setActiveNote(null);if(isMobile)setMobileView("list");}else if(activeFolder!=="dashboard"){setActiveFolder("dashboard");setActiveNote(null);}} }; window.addEventListener("popstate",handler);return()=>window.removeEventListener("popstate",handler); },[activeNote,activeFolder,isMobile]); if(loading)return
Loading...
; if(!user)return; const isTrashView=activeFolder==="trash"; /* โ”€โ”€ Back button for mobile โ”€โ”€ */ const backBtn=(label,onClick)=>; /* โ”€โ”€ Sidebar โ”€โ”€ */ const renderSidebar=()=>(
{isMobile&&sidebarOpen&&
setSidebarOpen(false)} style={{position:"absolute",inset:0,background:"rgba(0,0,0,0.4)",zIndex:-1}}/>}
โœ๏ธ
InkNote
{I.search} setSearchQuery(e.target.value)} placeholder="Search notes..." style={{border:"none",background:"transparent",outline:"none",flex:1,color:T.text,fontSize:13,fontFamily:"'DM Sans',sans-serif"}}/>
{DEFAULT_FOLDERS.map(f=>( ))}
Notebooks
{notebooks.map(nb=>(
handleNbDragStart(e,nb.id)} onDragOver={e=>handleNbDragOver(e,nb.id)} onDrop={e=>handleNbDrop(e,nb.id)} onDragEnd={handleNbDragEnd} onContextMenu={e=>handleNbContextMenu(e,nb)} style={{display:"flex",alignItems:"center",borderRadius:10,transition:"all .15s",background:dragOverNbId===nb.id?T.accentSoft:activeFolder===nb.id?T.sidebarActive:"transparent",borderTop:dragOverNbId===nb.id?`2px solid ${T.accent}`:"2px solid transparent"}}> {!isMobile&&{I.grip}}
))}
{installPrompt&&}
โ— {syncStatus==="synced"?"All synced":syncStatus==="syncing"?"Syncing...":syncStatus==="offline"?"Offline mode":syncStatus==="pending"?"Changes pending":"Sync error"} {!sync.isOnline()&&offline}
); /* โ”€โ”€ Dashboard โ”€โ”€ */ const renderDashboard=()=>(

My Notebooks

{notebooks.map(nb=>( n.folderId===nb.id).length} onClick={()=>{setActiveFolder(nb.id);setActiveNote(null);}} onContextMenu={e=>handleNbContextMenu(e,nb)} isActive={activeFolder===nb.id} isDragOver={dragOverNbId===nb.id} draggable={!isMobile} onDragStart={e=>handleNbDragStart(e,nb.id)} onDragEnd={handleNbDragEnd} onDragOver={e=>handleNbDragOver(e,nb.id)} onDrop={e=>handleNbDrop(e,nb.id)}/> ))}

Recent Notes

{activeNotes.slice(0,6).map(note=>{const nb=notebooks.find(b=>b.id===note.folderId);return( );})}
); /* โ”€โ”€ Note List โ”€โ”€ */ const renderNoteList=()=>{ const fname=(()=>{const sf=DEFAULT_FOLDERS.find(f=>f.id===activeFolder);if(sf)return sf.name;const nb=notebooks.find(n=>n.id===activeFolder);return nb?nb.name:"Notes";})(); // On mobile, hide list when editor is open if(isMobile&&mobileView==="editor"&&activeNote)return null; return(

{fname}

{!isTrashView&&} {isTrashView&&trashedNotes.length>0&&}
{filteredNotes.length===0?(
{isTrashView?"๐Ÿ—‘๏ธ":"๐Ÿ“"}
{isTrashView?"Trash is empty":"No notes yet"}
{!isTrashView&&}
):filteredNotes.map(note=>( ))}
); }; /* โ”€โ”€ Editor โ”€โ”€ */ const renderEditor=()=>{ // On mobile, hide editor when list is shown if(isMobile&&mobileView!=="editor")return null; if(!currentNoteSafe)return isMobile?null:(
โœ๏ธ
Select a note to edit
{!isTrashView&&}
); if(isTrashView&¤tNoteMeta)return(
{isMobile&&backBtn("Back",()=>{setActiveNote(null);setMobileView("list");})} {currentNoteMeta.title}
Trashed notes are read-only. Restore to edit.
); return(
{isMobile&&backBtn("Back",()=>{setActiveNote(null);setMobileView("list");})} updateNote(currentNoteSafe.id,{title:e.target.value})} style={{flex:1,border:"none",background:"transparent",outline:"none",fontSize:isMobile?15:17,fontWeight:600,color:T.text,fontFamily:"'Newsreader',Georgia,serif",minWidth:0}}/>
updateNote(currentNoteSafe.id,u)} dark={dark}/>
); }; return(
{renderSidebar()} {activeFolder==="dashboard"?renderDashboard():(<>{renderNoteList()}{renderEditor()})} {showNewNb&&setShowNewNb(false)} onCreate={createNb} dark={dark}/>} {editingNb&&setEditingNb(null)} onSave={u=>updateNb(editingNb.id,u)} dark={dark}/>} {showAccount&&setShowAccount(false)} onLogout={()=>{setShowAccount(false);onLogout();}} onUserUpdate={u=>setUser(u)} dark={dark}/>} {confirmDialog&&setConfirmDialog(null)} dark={dark}/>} {ctxMenu&&setCtxMenu(null)} dark={dark}/>}
); }