`); win.document.close(); }; window.doExportMarkdown=()=>{ if(!isPro()){openModal("pro-gate",{feature:"export_md"});return;} const m=gmap();if(!m)return; S.showExportMenu=false; const nodes=m.nodes; function toMD(pid,depth=0){ return nodes.filter(n=>n.pid===pid).map(n=>{ const prefix=depth===0?"## ":" ".repeat(depth-1)+"- "; return prefix+n.title+(n.done?" ✅":"")+"\n"+toMD(n.id,depth+1); }).join(""); } const roots=nodes.filter(n=>!n.pid); const md=`# ${m.title}\n\n`+roots.map(root=>toMD(root.id,0)).join("\n"); const blob=new Blob([md],{type:"text/markdown"}); const a=document.createElement("a"); a.href=URL.createObjectURL(blob); a.download=(m.title||"mindmap")+".md"; a.click();URL.revokeObjectURL(a.href); }; window.doExportJSON=()=>{ const m=gmap();if(!m)return; S.showExportMenu=false; const blob=new Blob([JSON.stringify({title:m.title,nodes:m.nodes,exportedAt:new Date().toISOString()},null,2)],{type:"application/json"}); const a=document.createElement("a"); a.href=URL.createObjectURL(blob); a.download=(m.title||"mindmap")+".json"; a.click();URL.revokeObjectURL(a.href); }; // ══ BUILD VIEWS ═══════════════════════════════════════════════════════════════ function buildHome(){ const syncBanner=S.fbUser ?`
${S.fbUser.photoURL?``:`
${esc((S.fbUser.displayName||"?")[0].toUpperCase())}
`}
☁ CLOUD SYNC — Phase 3
${esc(S.fbUser.displayName||S.fbUser.email)}
${esc(S.fbConfig.projectId)} · ${S.lastSyncAt?"Sync lúc "+new Date(S.lastSyncAt).toLocaleTimeString("vi-VN"):"Chưa sync"}
` :`
☁️
☁ CLOUD SYNC — Phase 3
Đồng bộ mindmap lên cloud
Đăng nhập Google để lưu & đồng bộ giữa các thiết bị
`; return`
✦ AI Generator
Tạo MindMap BĐS bằng AI
Nhập yêu cầu tiếng Việt → AI tạo mindmap hoàn chỉnh
${mapLimitLabel()}
${!isPro()?``:""}
${syncBanner}
MindMap của tôi
${S.maps.length} sơ đồ
${buildCards()}
`; } function buildCards(){ const f=S.maps.filter(m=>m.title.toLowerCase().includes(S.search.toLowerCase())); if(!f.length)return`
🗺️
${S.search?"Không tìm thấy kết quả":"Chưa có MindMap nào"}
${S.search?"Thử từ khóa khác hoặc xóa bộ lọc":"Bấm + Tạo mới, chọn từ Template, hoặc thử ✦ AI Generator!"}
${!S.search?``:""}
`; const favs=f.filter(m=>m.fav); let h=""; if(favs.length)h+=`
⭐ Yêu thích
${favs.map(cardHtml).join("")}
`; h+=`
Tất cả (${f.length})
${f.map(cardHtml).join("")}
`; return h; } // Color map for card color bar const CARD_COLORS={blue:"#2563eb",teal:"#0d9488",green:"#16a34a",purple:"#7c3aed",pink:"#db2777",red:"#dc2626",orange:"#ea580c",yellow:"#ca8a04",indigo:"#4f46e5",cyan:"#0891b2",lime:"#65a30d",default:"#334155"}; function cardHtml(m){ const ns=m.nodes.slice(0,14); const xs=ns.map(n=>n.x),ys=ns.map(n=>n.y); const mnx=Math.min(...xs)-20,mxx=Math.max(...xs)+20,mny=Math.min(...ys)-15,mxy=Math.max(...ys)+15; const sc=Math.min(190/(mxx-mnx||1),60/(mxy-mny||1)); let p=""; ns.filter(n=>n.pid).forEach(n=>{const pp=m.nodes.find(x=>x.id===n.pid);if(!pp)return;const c=gc(pp.col);p+=``;}); ns.forEach(n=>{const c=gc(n.col);p+=``;}); const lockBadge=m.pwdHash?`🔐`:""; const rootNode=m.nodes.find(n=>!n.pid); const barColor=CARD_COLORS[rootNode?.col]||CARD_COLORS.default; const crossCount=(m.crossLinks||[]).length; return`
${p}
${esc(m.title)}
${m.nodes.length} node${crossCount?` · ${crossCount} link`:""} · ${new Date(m.updatedAt).toLocaleDateString("vi-VN")} ${lockBadge}
`; } function buildTemplates(){ return`
Template BĐS
${S.tmpls.length} template
${S.tmpls.map(tplCard).join("")}
`; } function tplCard(t){ return`
${t.icon}
${esc(t.name)}
${t.desc?`
${esc(t.desc)}
`:""}
${t.nodes.length} node
`; } function buildEditor(){ const m=gmap();if(!m)return`
Không tìm thấy.
`; const aiP=S.showAI?`
✦ AIBĐS
${buildAIBody()}
`:""; return`
${buildZoomBar()}
Shift+click chọn nhiều · Kéo root = di chuyển cả chủ đề · Cuộn zoom · Chuột phải menu
Tên sơ đồ
⊞ Layout ${(()=>{const sel=gnode(S.selId);const m2=gmap();const rootCount=m2?.nodes.filter(n=>!n.pid).length||0;if(sel&&!sel.pid)return"— toàn map"+(rootCount>1?" ("+rootCount+" chủ đề)":"");return sel?"— nhánh: "+esc(sel.title.slice(0,18)):"— toàn map"+(rootCount>1?" ("+rootCount+" chủ đề)":"");})()}
${(()=>{const sel=gnode(S.selId);const m2=gmap();const rootCount=m2?.nodes.filter(n=>!n.pid).length||0; if(sel&&!sel.pid)return"Root \""+esc(sel.title.slice(0,16))+"\""+(rootCount>1?" — mỗi chủ đề tự layout riêng":" — áp dụng cho toàn map"); if(sel)return"Node \""+esc(sel.title.slice(0,20))+"\" — áp dụng cho nhánh con"; return rootCount>1?rootCount+" chủ đề độc lập — mỗi cây tự layout riêng":"Chưa chọn node — áp dụng toàn map"; })()}
${LAYOUTS.map(l=>``).join("")}
${buildNP()}
${aiP}`; } function buildNP(){ const n=gnode(S.selId); if(!n){ const m=gmap();const done=m?.nodes.filter(x=>x.done).length||0; return`

Chọn một node để chỉnh sửa.

Nhấp đôi để sửa tiêu đề trực tiếp.

Thống kê

${m?.nodes.length||0} node tổng

${done} đã hoàn thành

`; } const attTab=S.attTab||"note"; const links=n.links||[]; const images=n.images||[]; const hasNote=!!(n.description&&n.description.trim()); const attIndicators=[ hasNote?'📝':'', links.length?'🔗':'', images.length?'🖼':'', ].filter(Boolean).join(' '); return`
Tiêu đề (nhấp đôi node để sửa nhanh)
Màu sắc
${COLORS.map(c=>`
`).join("")}
Icon / Emoji
${n.icon||"—"} ${n.icon?``:""}
${buildIconPicker(n.id, n.icon||"")}
Đính kèm ${attIndicators?`${attIndicators}`:""}
${attTab==="note"?` `:""} ${attTab==="link"?`
${links.length===0?`
Chưa có link nào.
` :links.map((lk,i)=>``).join("")}
`:""} ${attTab==="img"?`
${images.map((img,i)=>`
${esc(img.name||
`).join("")}
${images.length===0?`
Chưa có ảnh.
`:""}`:""}
${n.pid?``:``}
${(()=>{ const hasKids=gmap()?.nodes.some(x=>x.pid===n.id); if(!hasKids) return""; const isC=!!S.collapsed[n.id]; return``; })()} ${n.pid?``:""}
${(()=>{ const cls=getCrossLinks().filter(cl=>cl.fromId===n.id||cl.toId===n.id); if(!cls.length) return""; return`
🔗 Cross Links (${cls.length})
${cls.map(cl=>{ const other=gnode(cl.fromId===n.id?cl.toId:cl.fromId); const dir=cl.fromId===n.id?"→":"←"; return`
${dir} ${esc((other?.title||"?").slice(0,20))}
`; }).join("")}
`; })()}`; } function buildAIBody(){ const activeP=S.aiProvider||"claude"; const cfg=S.settings.providers[activeP]; const hasKey=!!(cfg?.key); const providerHTML=PROVIDERS.map(p=>{ const hasK=!!(S.settings.providers[p.id]?.key); const isAct=activeP===p.id; return` ${p.icon} ${p.name.split(" ")[0]}${isAct?" ✓":""} `; }).join(""); return`
AI Provider
${providerHTML}
${!hasKey ?`
⚠️ Chưa có key.
` :`
Model: ${cfg?.model||"mặc định"}
` }
Gợi ý nhanh
${QUICK.map(p=>`${p}`).join("")}
Nhập yêu cầu
${mapLimitLabel()}
${S.aiMsg?`
${esc(S.aiMsg)}
`:""}`; } // ══ KNOWLEDGE HUB VIEW ═══════════════════════════════════════════════════════ const KB_CATS=[ {id:"all",label:"Tất cả"}, {id:"mindmap",label:"🗺 MindMap"}, {id:"prompt",label:"💬 Prompt"}, {id:"sop",label:"📋 SOP"}, {id:"script",label:"🎤 Kịch bản"}, {id:"note",label:"📝 Ghi chú"}, ]; const KB_TYPE_STYLE={ mindmap:{bg:"#0d2a1a",c:"#4ade80",label:"MindMap"}, prompt:{bg:"#1a1a2a",c:"#a78bfa",label:"Prompt"}, sop:{bg:"#2a1a0d",c:"#fb923c",label:"SOP"}, script:{bg:"#0d1a2a",c:"#38bdf8",label:"Kịch bản"}, note:{bg:"#1a1a1a",c:"#94a3b8",label:"Ghi chú"}, }; function buildKBView(){ if(!S.kbItems)S.kbItems=[]; const q=(S.kbSearch||"").toLowerCase(); const items=S.kbItems.filter(it=>{ if(S.kbCat!=="all"&&it.type!==S.kbCat)return false; if(q&&!it.title.toLowerCase().includes(q)&&!(it.content||"").toLowerCase().includes(q))return false; return true; }); const sidebar=`
📚 Knowledge Hub
${KB_CATS.map(c=>``).join("")}
`; return sidebar+`
${buildKBMain()}
`; } function buildKBMain(){ if(!S.kbItems)S.kbItems=[]; const q=(S.kbSearch||"").toLowerCase(); const items=S.kbItems.filter(it=>{ if(S.kbCat!=="all"&&it.type!==S.kbCat)return false; if(q&&!it.title.toLowerCase().includes(q)&&!(it.content||"").toLowerCase().includes(q))return false; return true; }); if(!items.length)return`
📭
Chưa có tài liệu nào
Bấm + Thêm tài liệu hoặc lưu SOP từ mindmap
`; return`
${items.map(it=>{ const ts=KB_TYPE_STYLE[it.type]||KB_TYPE_STYLE.note; return`
${ts.label}
${esc(it.title)}
${it.content?`
${esc(it.content)}
`:""}
${new Date(it.createdAt||Date.now()).toLocaleDateString("vi-VN")}
`; }).join("")}
`; } window.doDeleteKB=(id)=>{ if(!confirm("Xóa tài liệu này?"))return; S.kbItems=(S.kbItems||[]).filter(x=>x.id!==id); save(); const el=document.getElementById("kb-main-area"); if(el)el.innerHTML=buildKBMain(); }; window.saveMapToKB=()=>{ const m=gmap();if(!m)return; const sop=m.nodes.map(n=>n.title).join(" → "); const item={id:uid(),type:"sop",title:"SOP: "+m.title,content:sop,createdAt:Date.now()}; S.kbItems=[item,...(S.kbItems||[])]; save(); alert("✅ Đã lưu SOP vào Knowledge Hub!"); }; // ══ TRAINING CENTER VIEW ═════════════════════════════════════════════════════ function buildTCView(){ if(!S.tcCourses)S.tcCourses=clone(DEFAULT_COURSES); const courses=S.tcCourses; const LEVELS={Cơ_bản:{c:"type-mindmap"},Trung_cấp:{c:"type-prompt"},Nâng_cao:{c:"type-sop"}}; const sidebarItems=courses.map(course=>{ const total=course.lessons.length; const done=course.lessons.filter(l=>S.tcProgress[l.id]).length; const pct=total?Math.round(done/total*100):0; const isActive=S.tcCourseId===course.id; return`
${course.icon}
${esc(course.name)}
${done}/${total} bài ${pct}%
`; }).join(""); return`
🎓 Đào tạo
${sidebarItems||`
Chưa có khóa học.
`}
${buildTCMain()}
`; } function buildTCMain(){ if(!S.tcCourseId){ // Show overview if(!S.tcCourses)return""; const total=S.tcCourses.reduce((a,c)=>a+c.lessons.length,0); const done=Object.keys(S.tcProgress).filter(k=>S.tcProgress[k]).length; const pct=total?Math.round(done/total*100):0; return`
🎓 Training Center
Nâng cao kỹ năng môi giới BĐS của bạn
Tiến độ tổng thể
${pct}%
${done}/${total} bài học hoàn thành
← Chọn khóa học để bắt đầu
`; } if(S.tcLessonId) return buildTCLessonView(); const course=S.tcCourses?.find(x=>x.id===S.tcCourseId); if(!course)return""; const total=course.lessons.length; const done=course.lessons.filter(l=>S.tcProgress[l.id]).length; const pct=total?Math.round(done/total*100):0; const lessonItems=course.lessons.map((l,i)=>{ const isDone=!!S.tcProgress[l.id]; const preview=(l.desc||"").split("\n")[0].slice(0,80); return`
${isDone?"✓":i+1}
${esc(l.title)}
${esc(preview)}${(l.desc||"").length>80?"…":""}
${l.tags?.length?`
${l.tags.map(t=>`${esc(t)}`).join("")}
`:""} ${l.quiz?`✦ Có quiz`:""}
${isDone?"✓ Xong":"→"}
`; }).join(""); return`
${course.icon}
${esc(course.name)}
${esc(course.desc||"")} · Cấp độ: ${esc(course.level||"Cơ bản")} · ${done}/${total} bài · ${pct}%
${lessonItems||`
📖

Khóa học này chưa có bài học.

`}
`; } function buildTCLessonView(){ const course=S.tcCourses?.find(x=>x.id===S.tcCourseId); if(!course)return""; const lesson=course.lessons.find(l=>l.id===S.tcLessonId); if(!lesson)return""; const idx=course.lessons.findIndex(l=>l.id===S.tcLessonId); const prev=idx>0?course.lessons[idx-1]:null; const next=idx
🧠 Kiểm tra nhanh: ${esc(lesson.quiz.q)}
${lesson.quiz.opts.map((opt,i)=>``).join("")}
`:""; return`
${esc(lesson.title)}
Bài ${idx+1}/${course.lessons.length} · ${esc(course.name)}
${esc(lesson.desc)}
${quizHTML}
${prev?``:""} ${next?``:""}
`; } // ══ RENDER ALL ════════════════════════════════════════════════════════════════ function rall(){ const m=gmap(); document.getElementById("nav").innerHTML=` ${m?``:""}`; const providerActive=S.settings.providers[S.aiProvider||"claude"]; const keySet=!!(providerActive?.key); // Cloud badge const syncColors={idle:"var(--txt3)",syncing:"#f59e0b",ok:"var(--success2)",error:"var(--danger2)"}; const syncIcons={idle:"☁",syncing:"↻",ok:"✓",error:"✕"}; const syncColor=syncColors[S.syncStatus]||syncColors.idle; const syncIcon=syncIcons[S.syncStatus]||"☁"; const syncTitle=S.fbUser ?(S.syncStatus==="ok"?"Đồng bộ lúc "+new Date(S.lastSyncAt).toLocaleTimeString("vi-VN"):S.syncMsg||"Cloud Sync") :"Chưa đăng nhập Cloud"; // User avatar or cloud button const userHtml=S.fbUser ?`${S.fbUser.photoURL ?`${esc(S.fbUser.displayName||` :`
${esc((S.fbUser.displayName||"?")[0].toUpperCase())}
`}` :""; document.getElementById("tbar-right").innerHTML=` ${userHtml?`` :``} ${PROVIDERS.find(p=>p.id===(S.aiProvider||"claude"))?.icon||"🟠"} ${PROVIDERS.find(p=>p.id===(S.aiProvider||"claude"))?.name.split(" ")[0]||"Claude"} ${keySet?"":"(chưa có key)"} `; // ── Editor toolbar (separate bar below topbar) ──────────────────────────── const etbar=document.getElementById("editor-toolbar"); if(S.view==="editor"&&m){ etbar.style.display="flex"; refreshEditorToolbar(); }else{ etbar.style.display="none"; } const main=document.getElementById("main"); if(S.view==="home")main.innerHTML=buildHome(); else if(S.view==="templates")main.innerHTML=buildTemplates(); else if(S.view==="editor"){main.innerHTML=buildEditor();setTimeout(rsvg,30);} else if(S.view==="toolview"){main.innerHTML=buildToolView();} else if(S.view==="kb"){main.innerHTML=`
${buildKBView()}
`;} else if(S.view==="training"){main.innerHTML=`
${buildTCView()}
`;} } rall(); checkShareLink();