Forum
function currentAuthor(){
try{
Redirecting to /forum…
return (u?.display_name || u?.email || 'user');
}catch{ return 'user'; }
}
function applyAuthGate(){
const authed = isAuthed();
const newBtn = document.getElementById('createThread');
const titleIn = document.getElementById('newTitle');
const composer = document.getElementById('composer');
const composerGate = document.getElementById('composerGate');
const authCta = document.getElementById('authCta');
const saveCloudBtn = document.getElementById('saveCloud');
if(!authed){
newBtn.classList.add('composer-disabled');
titleIn.classList.add('composer-disabled');
composer.classList.add('composer-disabled');
composerGate.style.display='block';
authCta.style.display='block';
authCta.innerHTML = `
`;
if(saveCloudBtn) { saveCloudBtn.classList.add('composer-disabled'); saveCloudBtn.title='Login required to propose changes'; }
}else{
newBtn.classList.remove('composer-disabled');
titleIn.classList.remove('composer-disabled');
composer.classList.remove('composer-disabled');
composerGate.style.display='none';
authCta.style.display='none';
if(saveCloudBtn) { saveCloudBtn.classList.remove('composer-disabled'); saveCloudBtn.removeAttribute('title'); }
}
}
function load(){ try{ state.threads = JSON.parse(localStorage.getItem(LS_KEY)||'[]'); }catch{ state.threads=[] } }
function save(){ localStorage.setItem(LS_KEY, JSON.stringify(state.threads)); }
function id(){ return Math.random().toString(36).slice(2) + Date.now().toString(36); }
function renderThreads(){
const list = document.getElementById('threadList');
list.innerHTML = '';
if(state.threads.length===0){ list.innerHTML = '
No threads yet. Start one!
'; return; }
state.threads.forEach(t=>{
const div = document.createElement('div');
div.className='thread';
const initials = (t.author||'U').trim()[0]?.toUpperCase() || 'U';
div.innerHTML = `
${initials}
${escapeHtml(t.title)}
Started by ${escapeHtml(t.author||'user')}${t.posts.length} postsLast activity ${new Date(t.updated_at).toLocaleString()}
`;
div.onclick = (e)=>{ if(e.target.dataset.del){ deleteThread(e.target.dataset.del); e.stopPropagation(); return;} selectThread(t.id); };
list.appendChild(div);
});
}
function selectThread(tid){
state.selected = state.threads.find(x=>x.id===tid) || null;
const title = document.getElementById('threadTitle');
const meta = document.getElementById('threadMeta');
const list = document.getElementById('postList');
const comp = document.getElementById('composer');
if(!state.selected){ title.textContent='No thread selected'; meta.textContent='Select a thread to view posts'; list.innerHTML=''; comp.style.display='none'; return; }
title.textContent = state.selected.title;
meta.textContent = `${state.selected.posts.length} posts • Started by ${state.selected.author||'user'} • Created ${new Date(state.selected.created_at).toLocaleString()}`;
comp.style.display='block';
list.innerHTML='';
state.selected.posts.forEach(p=>{
const d = document.createElement('div');
d.className='post';
d.innerHTML = `
${escapeHtml(p.author||'user')}
${escapeHtml(p.body)}
${new Date(p.created_at).toLocaleString()}
`;
d.onclick=(e)=>{ if(e.target.dataset.delp){ deletePost(p.id); e.stopPropagation(); } };
list.appendChild(d);
});
}
function createThread(){
if(!isAuthed()) { return; }
const title = document.getElementById('newTitle').value.trim();
if(!title) return;
const t = { id:id(), title, author: currentAuthor(), created_at: Date.now(), updated_at: Date.now(), posts: [] };
state.threads.unshift(t); save(); renderThreads(); selectThread(t.id); document.getElementById('newTitle').value='';
}
function deleteThread(tid){ state.threads = state.threads.filter(t=>t.id!==tid); save(); renderThreads(); if(state.selected && state.selected.id===tid){ selectThread(null);} }
function addPost(){
if(!isAuthed()) { return; }
const body = document.getElementById('postBody').value.trim();
if(!body || !state.selected) return;
state.selected.posts.push({ id:id(), body, author: currentAuthor(), created_at: Date.now() });
state.selected.updated_at = Date.now();
save();
document.getElementById('postBody').value='';
renderThreads(); selectThread(state.selected.id);
}
function deletePost(pid){ if(!state.selected) return; state.selected.posts = state.selected.posts.filter(p=>p.id!==pid); state.selected.updated_at = Date.now(); save(); renderThreads(); selectThread(state.selected.id); }
function escapeHtml(s){ return s.replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"','\'':'''}[c])); }
// Export/Import
function exportData(){ const blob = new Blob([JSON.stringify(state.threads,null,2)], {type:'application/json'}); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'pisces-forum.json'; a.click(); }
async function importData(file){ const text = await file.text(); const arr = JSON.parse(text); if(Array.isArray(arr)){ state.threads = arr; save(); renderThreads(); selectThread(null); } }
// Init
load(); renderThreads();
document.getElementById('createThread').onclick = createThread;
document.getElementById('addPost').onclick = addPost;
document.getElementById('exportBtn').onclick = exportData;
document.getElementById('importFile').onchange = (e)=>{ if(e.target.files[0]) importData(e.target.files[0]); };
// Cloud helpers
async function loadFromCloud(){
try{
const { fetchCloudJson } = window.PiscesAICloud || {};
if(!fetchCloudJson) throw new Error('Cloud helpers not loaded');
const threads = await fetchCloudJson('data/forum/threads.json', []);
const posts = await fetchCloudJson('data/forum/posts.json', []);
const grouped = {};
posts.forEach(p=>{ (grouped[p.thread_id] = grouped[p.thread_id]||[]).push({ id:p.id, body:p.content||'', author:p.author||'user', created_at: Date.parse(p.created_at)||Date.now() }); });
state.threads = threads.map(t=>({ id:t.id, title:t.title, author: t.author||'user', created_at: Date.parse(t.created_at)||Date.now(), updated_at: Date.now(), posts: (grouped[t.id]||[]) }));
save(); renderThreads(); selectThread(null);
}catch(e){ alert('Failed to load cloud data'); console.warn(e); }
}
function saveToCloud(){
if(!isAuthed()) { return; }
const { openCloudSyncIssue } = window.PiscesAICloud || {};
if(!openCloudSyncIssue){ alert('Cloud helpers not loaded'); return; }
const threads = state.threads.map(t=>({ id:t.id, title:t.title, author:(t.author||'web'), created_at:new Date(t.created_at).toISOString() }));
const posts = state.threads.flatMap(t=> t.posts.map(p=>({ id:p.id, thread_id:t.id, author:(p.author||'web'), content:p.body, created_at:new Date(p.created_at).toISOString() })) );
const updates = [
{ path:'data/forum/threads.json', op:'merge', data: threads },
{ path:'data/forum/posts.json', op:'merge', data: posts }
];
openCloudSyncIssue('Forum update', updates, 'Proposed by website user');
}
document.getElementById('loadCloud').onclick = loadFromCloud;
document.getElementById('saveCloud').onclick = saveToCloud;
// Apply auth gating after DOM ready (app.js also runs DOMContentLoaded)
document.addEventListener('DOMContentLoaded', applyAuthGate);
// Also re-apply after a brief delay to ensure app.js updated state
setTimeout(applyAuthGate, 400);