(function(){ var REST = '/wp-json/wp/v2'; var CAT = 109; var STORAGE_KEY = 'sm_ee_creds_v1'; function $(s, p){return (p||document).querySelector(s);} function el(tag, attrs, kids){ var e = document.createElement(tag); if (attrs) for (var k in attrs){if (k==='class') e.className=attrs[k]; else if (k==='html') e.innerHTML=attrs[k]; else if (k==='on') {for(var ev in attrs[k]) e.addEventListener(ev, attrs[k][ev]);} else e.setAttribute(k, attrs[k]);} (kids||[]).forEach(function(k){ if(typeof k==='string') e.appendChild(document.createTextNode(k)); else if(k) e.appendChild(k); }); return e; } function msg(text, ok){ var area = $('#ee-msg-area'); area.innerHTML=''; area.appendChild(el('div',{class:'ee-msg '+(ok?'ee-ok':'ee-err'),html:text})); setTimeout(function(){area.innerHTML='';}, 6000); } function getCreds(){ try { return JSON.parse(localStorage.getItem(STORAGE_KEY)||'null'); } catch(e){ return null; } } function setCreds(u,p){ localStorage.setItem(STORAGE_KEY, JSON.stringify({u:u,p:p})); } function authHeader(){ var c = getCreds(); if (!c) return null; return 'Basic ' + btoa(c.u + ':' + c.p); } function fetchJSON(url, opts){ opts = opts || {}; opts.headers = opts.headers || {}; var ah = authHeader(); if (ah) opts.headers['Authorization'] = ah; return fetch(url, opts).then(function(r){ if (r.status === 401 || r.status === 403){ localStorage.removeItem(STORAGE_KEY); location.reload(); throw new Error('unauthorized'); } return r.json().then(function(j){ if (!r.ok) throw new Error(j.message || ('HTTP ' + r.status)); return j; }); }); } function parseExcerpt(post){ var raw = (post.excerpt && post.excerpt.raw) || (post.excerpt && post.excerpt.rendered ? post.excerpt.rendered.replace(/<[^>]+>/g,'') : '') || ''; var t = document.createElement('textarea'); t.innerHTML = raw; var s = t.value.replace(/[“”]/g,'"').replace(/[‘’]/g,"'").trim(); try { return JSON.parse(s); } catch(e){ return {}; } } function fmtDateBadge(iso){if(!iso) return '—'; var p=iso.split('-'); if(p.length!==3) return iso; return p[1]+'.'+p[2];} // ───── Login modal ───── function showLogin(){ var modal = el('div',{class:'ee-modal'}); var card = el('div',{class:'ee-modal-card',style:'max-width:440px;'}); card.innerHTML = '

🔐 Sign in to Events Editor

' + '

Enter your WordPress credentials. Use an Application Password (Users → Profile → Application Passwords), not your login password.

' + '' + '' + '
' + '
'; modal.appendChild(card); document.body.appendChild(modal); $('#ee-li-go').addEventListener('click', function(){ var u = $('#ee-li-u').value.trim(); var p = $('#ee-li-p').value.trim(); if (!u || !p) { $('#ee-li-err').innerHTML = '
Both fields required.
'; return; } setCreds(u, p); fetch(REST+'/users/me', {headers:{Authorization:'Basic '+btoa(u+':'+p)}}) .then(function(r){ return r.ok ? r.json().then(function(j){return {ok:true,j:j};}) : r.json().then(function(j){return {ok:false,j:j};}); }) .then(function(res){ if (res.ok){ modal.remove(); load(); } else { localStorage.removeItem(STORAGE_KEY); $('#ee-li-err').innerHTML = '
'+(res.j.message||'Login failed')+'
'; } }); }); } // ───── List ───── function load(){ var listEl = $('#ee-list'); listEl.innerHTML = '

Loading events…

'; fetchJSON(REST+'/posts?categories='+CAT+'&per_page=100&status=publish,draft,future&context=edit&_embed=wp:featuredmedia') .then(function(posts){ renderList(posts); }) .catch(function(e){ listEl.innerHTML = '

Load failed: '+e.message+'

'; }); } function renderList(posts){ var showPast = $('#ee-show-past').checked; var today = new Date(); today.setHours(0,0,0,0); var rows = posts.map(function(p){ p._meta = parseExcerpt(p); return p; }) .sort(function(a,b){ return (b._meta.date||'').localeCompare(a._meta.date||''); }); var listEl = $('#ee-list'); listEl.innerHTML = ''; var rendered = 0; rows.forEach(function(p){ var date = p._meta.date || ''; var isPast = date ? (new Date(date+'T00:00:00') < today) : false; if (isPast && !showPast) return; rendered++; var img = (p._embedded && p._embedded['wp:featuredmedia'] && p._embedded['wp:featuredmedia'][0] && p._embedded['wp:featuredmedia'][0].source_url) || ''; var thumb = el('div',{class:'ee-thumb'}, [img ? el('img',{src:img,alt:''}) : 'No image']); var dateBox = el('div',{}, [ el('div',{class:'ee-date'},[fmtDateBadge(date)]), el('div',{class:'ee-time'},[p._meta.time || '']) ]); var titleBox = el('div',{}, [ el('div',{class:'ee-title',html: p.title.rendered}), el('div',{class:'ee-tags'},(function(){ var t = []; t.push(el('span',{class:'ee-tag '+(isPast?'ee-past':'ee-upcoming')},[isPast?'Past':'Upcoming'])); if (p._meta.source === 'facebook' || p._meta.fb_event_id) t.push(el('span',{class:'ee-tag ee-fb'},['FB'])); if (p.status === 'draft') t.push(el('span',{class:'ee-tag ee-draft'},['Draft'])); if (p._meta.adopted_from_manual) t.push(el('span',{class:'ee-tag ee-fb'},['Adopted'])); return t; })()) ]); var actions = el('div',{class:'ee-actions'}, [ el('button',{class:'ee-btn ee-small', on:{click:function(){openEdit(p);}}},['Edit']), el('button',{class:'ee-btn ee-small ee-ghost', on:{click:function(){copyEvent(p);}}},['Copy']), el('button',{class:'ee-btn ee-small ee-danger', on:{click:function(){trashEvent(p);}}, title:'Move to draft (hides from public page)'},[p.status==='draft'?'Restore':'Hide']) ]); var row = el('div',{class:'ee-row '+(isPast?'ee-past':'')}, [thumb, dateBox, titleBox, actions]); listEl.appendChild(row); }); if (rendered === 0) listEl.innerHTML = '

No events to show. ' + (showPast?'':'Try enabling "Show past events".') + '

'; } // ───── Edit / Create / Copy ───── function blankPost(){ return {id:0, title:{raw:''}, content:{raw:''}, status:'publish', featured_media:0, _meta:{date:new Date().toISOString().slice(0,10), time:'7:00 PM', subtitle:'', price:'', cta_url:'', cta_text:'Reserve'}}; } function copyEvent(orig){ var copy = JSON.parse(JSON.stringify(orig)); copy.id = 0; copy.title.raw = (orig.title.raw || orig.title.rendered || '') + ' (copy)'; copy._meta.fb_event_id = ''; copy._meta.fb_event_url = ''; copy._meta.source = ''; copy._meta.adopted_from_manual = false; openEdit(copy); } function openEdit(post){ var isNew = !post.id; var modal = el('div',{class:'ee-modal'}); var card = el('div',{class:'ee-modal-card'}); card.innerHTML = '

'+(isNew?'New Event':'Edit Event #'+post.id)+'

' + '' + '
' + '
' + '' + '' + '
' + '
' + '' + '' + '
No image
' + '' + '
' + '' + '' + '
'; modal.appendChild(card); document.body.appendChild(modal); $('#ef-title').value = post.title.raw || post.title.rendered || ''; $('#ef-date').value = post._meta.date || ''; $('#ef-time').value = post._meta.time || ''; $('#ef-subtitle').value = post._meta.subtitle || ''; $('#ef-price').value = post._meta.price || ''; $('#ef-cta-url').value = post._meta.cta_url || ''; $('#ef-cta-text').value = post._meta.cta_text || 'Reserve'; $('#ef-content').value = post.content.raw || (post.content.rendered || '').replace(/<\/?p>/g,'').trim(); $('#ef-status').value = post.status || 'publish'; var currentMediaId = post.featured_media || 0; var imgUrl = (post._embedded && post._embedded['wp:featuredmedia'] && post._embedded['wp:featuredmedia'][0] && post._embedded['wp:featuredmedia'][0].source_url) || ''; if (imgUrl) $('#ef-img-prev').innerHTML = ''; $('#ef-cancel').addEventListener('click', function(){ modal.remove(); }); $('#ef-img-file').addEventListener('change', function(e){ var f = e.target.files[0]; if (!f) return; $('#ef-img-prev').innerHTML = 'Uploading…'; var headers = {'Authorization': authHeader(), 'Content-Disposition':'attachment; filename='+f.name, 'Content-Type': f.type || 'image/jpeg'}; fetch(REST+'/media', {method:'POST', headers:headers, body:f}) .then(function(r){return r.json();}) .then(function(m){ if(m.id){ currentMediaId = m.id; $('#ef-img-prev').innerHTML = ''; } else { $('#ef-img-prev').innerText='Upload failed'; } }); }); $('#ef-save').addEventListener('click', function(){ var meta = { date: $('#ef-date').value, time: $('#ef-time').value, subtitle: $('#ef-subtitle').value, price: $('#ef-price').value, cta_url: $('#ef-cta-url').value, cta_text: $('#ef-cta-text').value || 'Reserve' }; // Preserve FB linkage if editing FB-synced post if (post._meta.fb_event_id) { meta.fb_event_id = post._meta.fb_event_id; meta.fb_event_url = post._meta.fb_event_url; meta.source = post._meta.source; meta.last_sync = post._meta.last_sync; if (post._meta.adopted_from_manual) meta.adopted_from_manual = true; } var payload = { title: $('#ef-title').value, content: $('#ef-content').value, excerpt: JSON.stringify(meta), status: $('#ef-status').value, categories: [CAT], featured_media: currentMediaId || 0 }; var url = isNew ? REST+'/posts' : REST+'/posts/'+post.id; fetchJSON(url, {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(payload)}) .then(function(){ modal.remove(); msg('Saved ✓', true); load(); }) .catch(function(e){ $('#ef-err').innerHTML = '
'+e.message+'
'; }); }); } function trashEvent(post){ var newStatus = (post.status === 'draft') ? 'publish' : 'draft'; if (!confirm((newStatus==='draft'?'Hide':'Restore')+' "'+post.title.rendered+'"?')) return; fetchJSON(REST+'/posts/'+post.id, {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({status:newStatus})}) .then(function(){ msg('Status: '+newStatus, true); load(); }) .catch(function(e){ msg('Failed: '+e.message); }); } // ───── Init ───── document.addEventListener('DOMContentLoaded', function(){ $('#ee-new-btn').addEventListener('click', function(){ openEdit(blankPost()); }); $('#ee-reload-btn').addEventListener('click', load); $('#ee-show-past').addEventListener('change', load); $('#ee-logout-btn').addEventListener('click', function(){ if(confirm('Sign out and clear saved credentials?')){ localStorage.removeItem(STORAGE_KEY); location.reload(); } }); if (!getCreds()) showLogin(); else load(); }); if (document.readyState !== 'loading') { if (!getCreds()) showLogin(); else load(); } })();