1/* âââââââââââââââââââââââââââââââââââââââââââââââââââââââ 2 OWFR News Hub â JavaScript 3 Plugin: owfr-news-hub v2.2 4 Front-end rendering + admin inline CRUD 5 âââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 6document.addEventListener('DOMContentLoaded', function () { 7 'use strict'; 8 9 /* ââââââââââââââââââââââââââââââââââââââ */ 10 /* DISMISS LOADING OVERLAY */ 11 /* ââââââââââââââââââââââââââââââââââââââ */ 12 var _loader = document.getElementById('owfr-news-loading'); 13 if (_loader) { 14 _loader.classList.add('owfr-news-loading-done'); 15 setTimeout(function () { _loader.remove(); }, 400); 16 } 17 18 /* ââââââââââââââââââââââââââââââââââââââ */ 19 /* READ-MORE TOGGLE (delegated) */ 20 /* ââââââââââââââââââââââââââââââââââââââ */ 21 function initReadMoreBtns(container) { 22 container.querySelectorAll('.owfr-news-read-more').forEach(function (btn) { 23 if (btn.dataset.rmInit) return; 24 btn.dataset.rmInit = '1'; 25 var summary = btn.previousElementSibling; 26 if (!summary || !summary.classList.contains('owfr-news-card-summary')) return; 27 if (summary.scrollHeight > summary.clientHeight) { 28 btn.style.display = 'block'; 29 } 30 }); 31 } 32 33 document.addEventListener('click', function (e) { 34 var btn = e.target.closest('.owfr-news-read-more'); 35 if (!btn) return; 36 var summary = btn.previousElementSibling; 37 if (!summary || !summary.classList.contains('owfr-news-card-summary')) return; 38 var expanded = btn.getAttribute('aria-expanded') === 'true'; 39 summary.classList.toggle('is-expanded', !expanded); 40 btn.setAttribute('aria-expanded', String(!expanded)); 41 btn.textContent = expanded ? 'Read more' : 'Read less'; 42 }); 43 44 /* ââââââââââââââââââââââââââââââââââââââ */ 45 /* AUTO-ACTIVATE FIRST TAB ON LOAD */ 46 /* ââââââââââââââââââââââââââââââââââââââ */ 47 (function () { 48 var firstTab = document.querySelector('#owfr-news-tabs .owfr-news-tab-section'); 49 if (!firstTab) return; 50 var firstSection = firstTab.dataset.section; 51 firstTab.classList.add('active'); 52 var firstPanel = document.getElementById('owfr-news-panel-' + firstSection); 53 if (firstPanel) { 54 firstPanel.classList.add('active'); 55 requestAnimationFrame(function () { initReadMoreBtns(firstPanel); }); 56 } 57 }()); 58 59 /* ââââââââââââââââââââââââââââââââââââââ */ 60 /* TAB NAVIGATION */ 61 /* ââââââââââââââââââââââââââââââââââââââ */ 62 var _tabList = document.getElementById('owfr-news-tabs'); 63 if (_tabList) { 64 _tabList.addEventListener('click', function (e) { 65 var tab = e.target.closest('.owfr-news-tab'); 66 if (!tab || tab.classList.contains('owfr-news-admin-add-section')) return; 67 var section = tab.dataset.section; 68 var panelId = 'owfr-news-panel-' + section; 69 70 _tabList.querySelectorAll('.owfr-news-tab').forEach(function (t) { t.classList.remove('active'); }); 71 document.querySelectorAll('.owfr-news-panel').forEach(function (p) { p.classList.remove('active'); }); 72 73 tab.classList.add('active'); 74 var target = document.getElementById(panelId); 75 if (target) { 76 target.classList.add('active'); 77 requestAnimationFrame(function () { initReadMoreBtns(target); }); 78 } 79 80 /* scroll tab into view */ 81 var linkLeft = tab.offsetLeft - _tabList.offsetLeft; 82 var linkCenter = linkLeft - (_tabList.clientWidth / 2) + (tab.offsetWidth / 2); 83 _tabList.scrollTo({ left: linkCenter, behavior: 'smooth' }); 84 85 /* reset search when switching tabs */ 86 var searchInput = document.getElementById('owfr-news-search'); 87 if (searchInput && searchInput.value) { 88 filterCards(searchInput.value); 89 } 90 }); 91 } 92 93 /* ââââââââââââââââââââââââââââââââââââââ */ 94 /* SEARCH / FILTER */ 95 /* ââââââââââââââââââââââââââââââââââââââ */ 96 var searchInput = document.getElementById('owfr-news-search'); 97 var searchCount = document.getElementById('owfr-news-search-count'); 98 if (searchInput) { 99 var debounceTimer; 100 searchInput.addEventListener('input', function () { 101 clearTimeout(debounceTimer); 102 debounceTimer = setTimeout(function () { filterCards(searchInput.value); }, 200); 103 }); 104 } 105 106 function filterCards(query) { 107 var q = (query || '').toLowerCase().trim(); 108 var activePanel = document.querySelector('.owfr-news-panel.active'); 109 if (!activePanel) return; 110 var cards = activePanel.querySelectorAll('.owfr-news-card[data-item-id]'); 111 var visible = 0; 112 113 cards.forEach(function (card) { 114 if (!q) { 115 card.classList.remove('owfr-news-hidden'); 116 visible++; 117 return; 118 } 119 var text = card.textContent.toLowerCase(); 120 var section = (card.dataset.section || '').toLowerCase(); 121 if (text.indexOf(q) !== -1 || section.indexOf(q) !== -1) { 122 card.classList.remove('owfr-news-hidden'); 123 visible++; 124 } else { 125 card.classList.add('owfr-news-hidden'); 126 } 127 }); 128 129 if (searchCount) { 130 searchCount.textContent = q ? (visible + ' result' + (visible !== 1 ? 's' : '')) : ''; 131 } 132 } 133 134 /* ââââââââââââââââââââââââââââââââââââââ */ 135 /* ADMIN GATE â exit early if not admin */ 136 /* ââââââââââââââââââââââââââââââââââââââ */ 137 var isAdmin = document.body.classList.contains('admin-bar') || !!document.getElementById('wpadminbar'); 138 if (!isAdmin) return; 139 140 /* ââââââââââââââââââââââââââââââââââââââ */ 141 /* PARSE ADMIN CONFIG FROM wp_footer */ 142 /* ââââââââââââââââââââââââââââââââââââââ */ 143 function parseConfig(id) { 144 var el = document.getElementById(id); 145 if (!el) return null; 146 try { return JSON.parse(el.textContent); } catch (e) { return null; } 147 } 148 149 var config = parseConfig('owfr-news-admin-config'); 150 var orderConfig = parseConfig('owfr-news-order-config'); 151 var sectionsConfig = parseConfig('owfr-news-sections-config'); 152 var settingsConfig = parseConfig('owfr-news-settings-config'); 153 154 function esc(s) { return (s || '').replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>'); } 155 156 /* ââââââââââââââââââââââââââââââââââââââ */ 157 /* INJECT EDIT/DELETE BUTTONS ON CARDS */ 158 /* ââââââââââââââââââââââââââââââââââââââ */ 159 document.querySelectorAll('.owfr-news-card[data-item-id]').forEach(function (card) { 160 injectCardAdminButtons(card); 161 }); 162 163 function injectCardAdminButtons(card) { 164 card.style.position = 'relative'; 165 166 var editBtn = document.createElement('button'); 167 editBtn.className = 'owfr-news-admin-edit-btn'; 168 editBtn.type = 'button'; 169 editBtn.textContent = 'â Edit'; 170 editBtn.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); openItemEditor(card); }); 171 card.appendChild(editBtn); 172 173 var delBtn = document.createElement('button'); 174 delBtn.className = 'owfr-news-admin-delete-btn'; 175 delBtn.type = 'button'; 176 delBtn.textContent = 'â Delete'; 177 delBtn.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); deleteItem(card); }); 178 card.appendChild(delBtn); 179 180 injectDragHandle(card); 181 } 182 183 /* ââââââââââââââââââââââââââââââââââââââ */ 184 /* INJECT "ADD CARD" IN EACH GRID */ 185 /* ââââââââââââââââââââââââââââââââââââââ */ 186 document.querySelectorAll('.owfr-news-panel').forEach(function (panel) { 187 var grid = panel.querySelector('.owfr-news-grid'); 188 if (!grid) return; 189 var sectionId = panel.id.replace('owfr-news-panel-', ''); 190 /* skip the "all" panel â items are added per section */ 191 if (sectionId === 'all') return; 192 var addBtn = document.createElement('button'); 193 addBtn.className = 'owfr-news-admin-add-card'; 194 addBtn.type = 'button'; 195 addBtn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg><span>Add New Item</span>'; 196 addBtn.addEventListener('click', function () { openNewItemEditor(sectionId, grid); }); 197 grid.appendChild(addBtn); 198 }); 199 200 /* ââââââââââââââââââââââââââââââââââââââ */ 201 /* HERO EDIT BUTTON */ 202 /* ââââââââââââââââââââââââââââââââââââââ */ 203 var heroEl = document.querySelector('.owfr-news-hero[data-hero-id]'); 204 if (heroEl) { 205 var heroEditBtn = document.createElement('button'); 206 heroEditBtn.className = 'owfr-news-admin-hero-edit-btn'; 207 heroEditBtn.type = 'button'; 208 heroEditBtn.textContent = 'â Edit Hero'; 209 heroEditBtn.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); openHeroEditor(heroEl); }); 210 heroEl.appendChild(heroEditBtn); 211 } 212 213 /* ââââââââââââââââââââââââââââââââââââââ */ 214 /* WELCOME BANNER EDIT BUTTON */ 215 /* ââââââââââââââââââââââââââââââââââââââ */ 216 var welcomeEl = document.getElementById('owfr-news-welcome'); 217 if (welcomeEl) { 218 welcomeEl.style.position = 'relative'; 219 var welcomeEditBtn = document.createElement('button'); 220 welcomeEditBtn.className = 'owfr-news-admin-hero-edit-btn'; 221 welcomeEditBtn.type = 'button'; 222 welcomeEditBtn.textContent = 'â Edit Banner'; 223 welcomeEditBtn.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); openWelcomeEditor(welcomeEl); }); 224 welcomeEl.appendChild(welcomeEditBtn); 225 } 226 227 /* ââââââââââââââââââââââââââââââââââââââ */ 228 /* SECTION HEADER EDIT BUTTONS */ 229 /* ââââââââââââââââââââââââââââââââââââââ */ 230 document.querySelectorAll('.owfr-news-section-header[data-section-slug]').forEach(function (hdr) { 231 hdr.style.position = 'relative'; 232 233 var editBtn = document.createElement('button'); 234 editBtn.className = 'owfr-news-admin-section-edit-btn'; 235 editBtn.type = 'button'; 236 editBtn.textContent = 'â Edit'; 237 editBtn.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); openSectionEditor(hdr); }); 238 hdr.appendChild(editBtn); 239 240 var delBtn = document.createElement('button'); 241 delBtn.className = 'owfr-news-admin-section-delete-btn'; 242 delBtn.type = 'button'; 243 delBtn.textContent = 'â Delete'; 244 delBtn.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); deleteSection(hdr.dataset.sectionSlug); }); 245 hdr.appendChild(delBtn); 246 }); 247 248 /* ââââââââââââââââââââââââââââââââââââââ */ 249 /* ADD SECTION "+" BUTTON IN TAB BAR */ 250 /* ââââââââââââââââââââââââââââââââââââââ */ 251 (function () { 252 var tl = document.getElementById('owfr-news-tabs'); 253 if (!tl) return; 254 var li = document.createElement('li'); 255 li.className = 'owfr-news-add-section-li'; 256 var btn = document.createElement('button'); 257 btn.className = 'owfr-news-admin-add-section'; 258 btn.type = 'button'; 259 btn.title = 'Add New Section'; 260 btn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>'; 261 btn.addEventListener('click', openNewSectionEditor); 262 li.appendChild(btn); 263 tl.appendChild(li); 264 265 /* Delete "Ã" on each section tab */ 266 tl.querySelectorAll('.owfr-news-tab-section').forEach(function (tab) { 267 injectTabDeleteButton(tab); 268 }); 269 })(); 270 271 function injectTabDeleteButton(tabBtn) { 272 if (tabBtn.querySelector('.owfr-news-admin-delete-section-tab')) return; 273 var del = document.createElement('span'); 274 del.className = 'owfr-news-admin-delete-section-tab';
275 del.title = 'Delete section'; 276 del.innerHTML = '×'; 277 del.addEventListener('click', function (e) { 278 e.preventDefault(); e.stopPropagation(); 279 deleteSection(tabBtn.dataset.section); 280 }); 281 tabBtn.appendChild(del); 282 } 283 284 /* ââââââââââââââââââââââââââââââââââââââ */ 285 /* MODAL HELPERS */ 286 /* ââââââââââââââââââââââââââââââââââââââ */ 287 function closeModal() { 288 var m = document.getElementById('owfr-news-edit-modal'); 289 if (m) m.remove(); 290 document.body.style.overflow = ''; 291 } 292 293 function createModal(title, formHTML) { 294 closeModal(); 295 var m = document.createElement('div'); 296 m.id = 'owfr-news-edit-modal'; 297 m.innerHTML = '<div class="owfr-news-edit-overlay"></div>' 298 + '<div class="owfr-news-edit-panel">' 299 + '<div class="owfr-news-edit-panel-header"><h3>' + esc(title) + '</h3><button class="owfr-news-edit-close" aria-label="Close">×</button></div>' 300 + '<form class="owfr-news-edit-form">' 301 + formHTML 302 + '<div class="owfr-news-edit-actions">' 303 + '<button type="submit" class="owfr-news-btn owfr-news-btn-primary" style="flex:1">Save</button>' 304 + '<button type="button" class="owfr-news-edit-cancel owfr-news-btn owfr-news-btn-secondary" style="flex:1">Cancel</button>' 305 + '</div>' 306 + '<div class="owfr-news-edit-status"></div>' 307 + '</form></div>'; 308 309 document.body.appendChild(m); 310 document.body.style.overflow = 'hidden'; 311 312 m.querySelector('.owfr-news-edit-close').addEventListener('click', closeModal); 313 m.querySelector('.owfr-news-edit-cancel').addEventListener('click', closeModal); 314 315 attachMediaPickers(m); 316 return m; 317 } 318 319 function fieldHTML(name, label, value, type, hint) { 320 var isFile = (type === 'url' && (name === 'image' || name === 'file_url' || name === 'bottom_image')); 321 var html = '<div class="owfr-news-edit-field"><label>' + label; 322 if (hint) html += ' <span class="owfr-news-label-hint">(' + hint + ')</span>'; 323 html += '</label>'; 324 if (isFile) { 325 html += '<div style="display:flex;gap:0.5rem;align-items:center">'; 326 html += '<input type="url" name="' + name + '" value="' + esc(value) + '" placeholder="Paste URL or choose from Media Library" style="flex:1" />'; 327 html += '<button type="button" class="owfr-news-btn owfr-news-btn-secondary owfr-news-media-pick" data-target="' + name + '" style="white-space:nowrap;padding:0.4rem 0.7rem;font-size:0.82rem">ð Media</button>'; 328 html += '</div>'; 329 if (value) html += '<img class="owfr-news-img-preview-' + name + '" src="' + esc(value) + '" style="max-height:50px;margin-top:0.4rem;border-radius:4px;border:1px solid #eee" />'; 330 } else if (type === 'textarea') { 331 html += '<textarea name="' + name + '" rows="3">' + esc(value) + '</textarea>'; 332 } else if (type === 'select') { 333 /* value = JSON string of options array [{value, label}] */ 334 /* actual selected value passed via hint overload â re-parse */ 335 html += value; /* pre-built <select> HTML */ 336 } else { 337 html += '<input type="' + type + '" name="' + name + '" value="' + esc(value) + '" />'; 338 } 339 html += '</div>'; 340 return html; 341 } 342 343 function buildSectionSelect(name, selectedSlug) { 344 var sections = document.querySelectorAll('.owfr-news-tab-section'); 345 var html = '<select name="' + name + '">'; 346 html += '<option value="">â Select section â</option>'; 347 sections.forEach(function (tab) { 348 var slug = tab.dataset.section; 349 var label = tab.textContent.replace(/Ã$/, '').trim(); 350 html += '<option value="' + esc(slug) + '"' + (slug === selectedSlug ? ' selected' : '') + '>' + esc(label) + '</option>'; 351 }); 352 html += '</select>'; 353 return html; 354 } 355 356 /* ââââââââââââââââââââââââââââââââââââââ */ 357 /* CARD LAYOUT HELPER */ 358 /* ââââââââââââââââââââââââââââââââââââââ */ 359 function getCardLayout(card) { 360 if (card.classList.contains('owfr-news-card-wide')) return 'owfr-news-card-wide'; 361 if (card.classList.contains('owfr-news-card-half')) return 'owfr-news-card-half'; 362 return ''; 363 } 364 365 function buildLayoutPicker(currentLayout) { 366 var html = '<div class="owfr-news-edit-field"><label>Card Size</label>'; 367 html += '<div class="owfr-news-layout-picker">'; 368 html += '<button type="button" class="owfr-news-layout-opt' + (currentLayout === '' ? ' selected' : '') + '" data-layout="">Standard</button>'; 369 html += '<button type="button" class="owfr-news-layout-opt' + (currentLayout === 'owfr-news-card-half' ? ' selected' : '') + '" data-layout="owfr-news-card-half">Half Width</button>'; 370 html += '<button type="button" class="owfr-news-layout-opt' + (currentLayout === 'owfr-news-card-wide' ? ' selected' : '') + '" data-layout="owfr-news-card-wide">Full Width</button>'; 371 html += '</div><input type="hidden" name="layout" value="' + esc(currentLayout) + '" /></div>'; 372 return html; 373 } 374 375 function initLayoutPicker(modal) { 376 modal.querySelectorAll('.owfr-news-layout-opt').forEach(function (opt) { 377 opt.addEventListener('click', function () { 378 modal.querySelectorAll('.owfr-news-layout-opt').forEach(function (o) { o.classList.remove('selected'); }); 379 this.classList.add('selected'); 380 modal.querySelector('[name="layout"]').value = this.dataset.layout; 381 }); 382 }); 383 } 384 385 /* ââââââââââââââââââââââââââââââââââââââ */ 386 /* FILES REPEATER (EN / FR tabs) */ 387 /* ââââââââââââââââââââââââââââââââââââââ */ 388 function buildFilesRepeater(filesEn, filesFr) { 389 var html = '<div class="owfr-news-edit-field">'; 390 html += '<label>Attached Files / Documents <span class="owfr-news-label-hint">(by language)</span></label>'; 391 html += '<div class="owfr-news-files-tab-bar">'; 392 html += '<button type="button" class="owfr-news-files-tab active" data-files-tab="en">ð¬ð§ English</button>'; 393 html += '<button type="button" class="owfr-news-files-tab" data-files-tab="fr">ð«ð· Français</button>'; 394 html += '</div>'; 395 /* EN panel */ 396 html += '<div class="owfr-news-files-panel active" data-files-panel="en">'; 397 html += '<div class="owfr-news-files-list" data-lang="en">'; 398 if (filesEn && filesEn.length) { 399 filesEn.forEach(function (fi, i) { html += fileRowHTML(fi.url || '', fi.label || '', i); }); 400 } 401 html += '</div>'; 402 html += '<button type="button" class="owfr-news-btn owfr-news-btn-secondary owfr-news-add-file-btn" data-lang="en" style="margin-top:0.5rem;padding:0.45rem 1rem;font-size:0.82rem">+ Add English File</button>'; 403 html += '</div>'; 404 /* FR panel */ 405 html += '<div class="owfr-news-files-panel" data-files-panel="fr">'; 406 html += '<div class="owfr-news-files-list" data-lang="fr">'; 407 if (filesFr && filesFr.length) { 408 filesFr.forEach(function (fi, i) { html += fileRowHTML(fi.url || '', fi.label || '', i); }); 409 } 410 html += '</div>'; 411 html += '<button type="button" class="owfr-news-btn owfr-news-btn-secondary owfr-news-add-file-btn" data-lang="fr" style="margin-top:0.5rem;padding:0.45rem 1rem;font-size:0.82rem">+ Add French File</button>'; 412 html += '</div>'; 413 html += '</div>'; 414 return html; 415 } 416 417 function fileRowHTML(url, label, idx) { 418 return '<div class="owfr-news-file-row" data-idx="' + idx + '" style="display:flex;gap:0.4rem;align-items:center;margin-bottom:0.4rem">' 419 + '<input type="text" class="owfr-news-file-row-label" value="' + esc(label) + '" placeholder="Label (e.g. Budget Report)" style="flex:1;min-width:0;padding:0.4rem 0.6rem;border:1px solid #ccc;border-radius:5px;font-size:0.85rem" />' 420 + '<input type="url" class="owfr-news-file-row-url" value="' + esc(url) + '" placeholder="File URL" style="flex:1.5;min-width:0;padding:0.4rem 0.6rem;border:1px solid #ccc;border-radius:5px;font-size:0.85rem" />' 421 + '<button type="button" class="owfr-news-btn owfr-news-btn-secondary owfr-news-file-row-media" style="white-space:nowrap;padding:0.35rem 0.55rem;
421font-size:0.78rem" title="Choose from Media Library">ð</button>' 422 + '<button type="button" class="owfr-news-file-row-remove" style="background:#c00;color:#fff;border:none;border-radius:5px;width:28px;height:28px;cursor:pointer;font-size:14px;line-height:1;flex-shrink:0" title="Remove">×</button>' 423 + '</div>'; 424 } 425 426 function initFilesRepeater(modal) { 427 /* Tab switching */ 428 modal.querySelectorAll('.owfr-news-files-tab').forEach(function (tab) { 429 tab.addEventListener('click', function () { 430 modal.querySelectorAll('.owfr-news-files-tab').forEach(function (t) { t.classList.remove('active'); }); 431 modal.querySelectorAll('.owfr-news-files-panel').forEach(function (p) { p.classList.remove('active'); }); 432 this.classList.add('active'); 433 var panel = modal.querySelector('[data-files-panel="' + this.dataset.filesTab + '"]'); 434 if (panel) panel.classList.add('active'); 435 }); 436 }); 437 438 /* Add buttons for each language */ 439 modal.querySelectorAll('.owfr-news-add-file-btn').forEach(function (addBtn) { 440 var lang = addBtn.dataset.lang; 441 var list = modal.querySelector('.owfr-news-files-list[data-lang="' + lang + '"]'); 442 if (!list) return; 443 addBtn.addEventListener('click', function () { 444 var idx = list.querySelectorAll('.owfr-news-file-row').length; 445 var tmp = document.createElement('div'); 446 tmp.innerHTML = fileRowHTML('', '', idx); 447 var row = tmp.firstChild; 448 list.appendChild(row); 449 attachFileRowEvents(row, modal); 450 row.querySelector('.owfr-news-file-row-label').focus(); 451 }); 452 }); 453 454 modal.querySelectorAll('.owfr-news-file-row').forEach(function (row) { 455 attachFileRowEvents(row, modal); 456 }); 457 } 458 459 function attachFileRowEvents(row, modal) { 460 row.querySelector('.owfr-news-file-row-remove').addEventListener('click', function () { row.remove(); }); 461 var mediaBtn = row.querySelector('.owfr-news-file-row-media'); 462 if (mediaBtn) { 463 mediaBtn.addEventListener('click', function () { 464 if (typeof wp === 'undefined' || !wp.media) { alert('WordPress Media Library not available.'); return; } 465 var frame = wp.media({ title: 'Choose File', button: { text: 'Use this file' }, multiple: false }); 466 frame.on('select', function () { 467 var att = frame.state().get('selection').first().toJSON(); 468 row.querySelector('.owfr-news-file-row-url').value = att.url; 469 if (!row.querySelector('.owfr-news-file-row-label').value) { 470 row.querySelector('.owfr-news-file-row-label').value = att.title || att.filename || ''; 471 } 472 }); 473 frame.open(); 474 }); 475 } 476 } 477 478 /* ââââââââââââââââââââââââââââââââââââââ */ 479 /* PARTNER LOGOS REPEATER */ 480 /* ââââââââââââââââââââââââââââââââââââââ */ 481 function frontLogosRepeaterHTML(logos) { 482 var html = '<div class="owfr-news-edit-field">'; 483 html += '<label>Partner Logos <span class="owfr-news-label-hint">(display multiple logos side-by-side at top of card â overrides single image)</span></label>'; 484 html += '<div class="owfr-news-logos-list">'; 485 if (logos && logos.length) { 486 logos.forEach(function (url) { html += frontLogoRowHTML(url); }); 487 } 488 html += '</div>'; 489 html += '<button type="button" class="owfr-news-btn owfr-news-btn-secondary owfr-news-add-logo-btn" style="margin-top:0.5rem;padding:0.45rem 1rem;font-size:0.82rem">+ Add Logo</button>'; 490 html += '</div>'; 491 return html; 492 } 493 494 function frontLogoRowHTML(url) { 495 return '<div class="owfr-news-logo-row" style="display:flex;gap:0.4rem;align-items:center;margin-bottom:0.4rem">' 496 + '<input type="url" name="logos[]" value="' + esc(url || '') + '" placeholder="Logo image URL" style="flex:1;min-width:0;padding:0.4rem 0.6rem;border:1px solid #ccc;border-radius:5px;font-size:0.85rem" />' 497 + '<button type="button" class="owfr-news-btn owfr-news-btn-secondary owfr-news-logo-media-btn" style="white-space:nowrap;padding:0.35rem 0.55rem;
497font-size:0.78rem" title="Choose from Media Library">📁</button>' 498 + '<button type="button" class="owfr-news-logo-row-remove" style="background:#c00;color:#fff;border:none;border-radius:5px;width:28px;height:28px;cursor:pointer;font-size:14px;line-height:1;flex-shrink:0" title="Remove">×</button>' 499 + '</div>'; 500 } 501 502 function initFrontLogosRepeater(modal) { 503 modal.querySelectorAll('.owfr-news-logo-row').forEach(function (row) { 504 attachFrontLogoRowEvents(row); 505 }); 506 var addBtn = modal.querySelector('.owfr-news-add-logo-btn'); 507 if (addBtn) { 508 addBtn.addEventListener('click', function () { 509 var list = modal.querySelector('.owfr-news-logos-list'); 510 var tmp = document.createElement('div'); 511 tmp.innerHTML = frontLogoRowHTML(''); 512 var row = tmp.firstChild; 513 list.appendChild(row); 514 attachFrontLogoRowEvents(row); 515 row.querySelector('[name="logos[]"]').focus(); 516 }); 517 } 518 } 519 520 function attachFrontLogoRowEvents(row) { 521 var removeBtn = row.querySelector('.owfr-news-logo-row-remove'); 522 if (removeBtn) removeBtn.addEventListener('click', function () { row.remove(); }); 523 var mediaBtn = row.querySelector('.owfr-news-logo-media-btn'); 524 if (mediaBtn) { 525 mediaBtn.addEventListener('click', function () { 526 if (typeof wp === 'undefined' || !wp.media) { alert('WordPress Media Library not available.'); return; } 527 var input = row.querySelector('[name="logos[]"]'); 528 var frame = wp.media({ title: 'Choose Logo', button: { text: 'Use this image' }, library: { type: 'image' }, multiple: false }); 529 frame.on('select', function () { input.value = frame.state().get('selection').first().toJSON().url; }); 530 frame.open(); 531 }); 532 } 533 } 534 535 /* ââââââââââââââââââââââââââââââââââââââ */ 536 /* MEDIA LIBRARY PICKER */ 537 /* ââââââââââââââââââââââââââââââââââââââ */ 538 function attachMediaPickers(modal) { 539 modal.querySelectorAll('.owfr-news-media-pick').forEach(function (btn) { 540 btn.addEventListener('click', function () { 541 if (typeof wp === 'undefined' || !wp.media) { alert('WordPress Media Library not available.'); return; } 542 var targetName = btn.dataset.target; 543 var fileType = (targetName === 'file_url') ? '' : 'image'; 544 var frame = wp.media({ 545 title: targetName === 'file_url' ? 'Choose File' : 'Choose Image', 546 button: { text: 'Use this file' }, 547 multiple: false, 548 library: fileType ? { type: fileType } : {} 549 }); 550 frame.on('select', function () { 551 var att = frame.state().get('selection').first().toJSON(); 552 var input = modal.querySelector('[name="' + targetName + '"]'); 553 if (input) input.value = att.url; 554 var preview = modal.querySelector('.owfr-news-img-preview-' + targetName); 555 if (preview) { 556 preview.src = att.url; 557 } else if (targetName === 'image') { 558 var img = document.createElement('img'); 559 img.className = 'owfr-news-img-preview-' + targetName; 560 img.src = att.url; 561 img.style.cssText = 'max-height:50px;margin-top:0.4rem;border-radius:4px;border:1px solid #eee'; 562 if (input) input.closest('.owfr-news-edit-field').appendChild(img); 563 } 564 }); 565 frame.open(); 566 }); 567 }); 568 } 569 570 /* ââââââââââââââââââââââââââââââââââââââ */ 571 /* MODAL: EDIT ITEM */ 572 /* ââââââââââââââââââââââââââââââââââââââ */ 573 function getCardData(card) { 574 var d = {}; 575 var el; 576 el = card.querySelector('.owfr-news-card-image'); if (el) d.image = el.getAttribute('src'); 577 el = card.querySelector('h3'); if (el) d.title = el.textContent; 578 el = card.querySelector('.owfr-news-card-summary'); if (el) d.summary = el.textContent.trim(); 579 d.section = card.dataset.section || ''; 580 d.featured = card.classList.contains('owfr-news-card-featured'); 581 el = card.querySelector('.owfr-news-date'); if (el) d.event_date = el.textContent.trim(); 582 el = card.querySelector('.owfr-news-source'); if (el) d.source_label = el.textContent.trim(); 583 el = card.querySelector('.owfr-news-lang'); if (el) d.language = el.textContent.trim(); 584 /* extract link URLs */ 585 el = card.querySelector('.owfr-news-btn-primary'); if (el) { d.external_link = el.getAttribute('href'); d.button_text = el.textContent.trim(); } 586 el = card.querySelector('.owfr-news-btn-secondary'); if (el) d.file_url = el.getAttribute('href'); 587 /* extract bottom image */ 588 el = card.querySelector('.owfr-news-card-bottom-image'); if (el) d.bottom_image = el.getAttribute('src'); 589 /* extract layout */ 590 d._layout = getCardLayout(card); 591 /* extract EN/FR files */ 592 d.files_en = []; d.files_fr = []; 593 card.querySelectorAll('.owfr-news-file-link[data-lang="en"]').forEach(function (a) { 594 d.files_en.push({ url: a.getAttribute('href'), label: a.textContent.trim() }); 595 }); 596 card.querySelectorAll('.owfr-news-file-link[data-lang="fr"]').forEach(function (a) { 597 d.files_fr.push({ url: a.getAttribute('href'), label: a.textContent.trim() }); 598 }); 599 /* extract partner logos */ 600 d.logos = []; 601 card.querySelectorAll('.owfr-news-logo-img').forEach(function (img) { 602 d.logos.push(img.getAttribute('src')); 603 }); 604 return d; 605 } 606 607 function openItemEditor(card) { 608 var itemId = card.dataset.itemId; 609 var cur = getCardData(card); 610 611 var f = ''; 612 f += fieldHTML('image', 'Single Image URL (fallback)', cur.image || '', 'url'); 613 f += frontLogosRepeaterHTML(cur.logos || []); 614 f += fieldHTML('title', 'Title', cur.title || '', 'text'); 615 f += '<div class="owfr-news-edit-field"><label>Summary <span class="owfr-news-label-hint">(displayed on card)</span></label>' 616 + '<textarea name="summary" rows="3">' + esc(cur.summary || '') + '</textarea></div>'; 617 f += '<div class="owfr-news-edit-field"><label>Section</label>' + buildSectionSelect('section', cur.section) + '</div>'; 618 f += fieldHTML('event_date', 'Date', cur.event_date || '', 'date'); 619 f += fieldHTML('source_label', 'Source Label', cur.source_label || '', 'text', 'e.g. OWFR, Ministry of Health'); 620 f += fieldHTML('language', 'Language', cur.language || '', 'text', 'e.g. EN, FR, Bilingual'); 621 f += fieldHTML('external_link', 'External Link URL', cur.external_link || '', 'url'); 622 f += fieldHTML('button_text', 'Button Text', cur.button_text || 'Learn More', 'text'); 623 f += fieldHTML('file_url', 'File / Document URL', cur.file_url || '', 'url'); 624 f += buildLayoutPicker(cur._layout || ''); 625 f += buildFilesRepeater(cur.files_en || [], cur.files_fr || []); 626 f += fieldHTML('bottom_image', 'Bottom Image URL', cur.bottom_image || '', 'url'); 627 f += '<div class="owfr-news-edit-toggle">' 628 + '<input type="checkbox" name="featured" id="owfr-news-feat-chk"' + (cur.featured ? ' checked' : '') + ' />' 629 + '<label for="owfr-news-feat-chk">â Featured Item</label></div>'; 630 631 var m = createModal('Edit Item', f); 632 initLayoutPicker(m); 633 initFilesRepeater(m); 634 initFrontLogosRepeater(m); 635 636 m.querySelector('.owfr-news-edit-form').addEventListener('submit', function (e) { 637 e.preventDefault(); 638 handleItemSave(m, itemId, card); 639 }); 640 } 641 642 function openNewItemEditor(sectionSlug, grid) { 643 var itemId = 'news-' + Date.now(); 644 645 var f = ''; 646 f += fieldHTML('image', 'Single Image URL (fallback)', '', 'url'); 647 f += frontLogosRepeaterHTML([]); 648 f += fieldHTML('title', 'Title', '', 'text'); 649 f += '<div class="owfr-news-edit-field"><label>Summary</label><textarea name="summary" rows="3" placeholder="Brief description of this update"></textarea></div>'; 650 f += '<div class="owfr-news-edit-field"><label>Section</label>' + buildSectionSelect('section', sectionSlug) + '</div>'; 651 f += fieldHTML('event_date', 'Date', '', 'date'); 652 f += fieldHTML('source_label', 'Source Label', '', 'text', 'e.g. OWFR, Ministry of Health'); 653 f += fieldHTML('language', 'Language', '', 'text', 'e.g. EN, FR, Bilingual'); 654 f += fieldHTML('external_link', 'External Link URL', '', 'url'); 655 f += fieldHTML('button_text', 'Button Text', 'Learn More', 'text'); 656 f += fieldHTML('file_url', 'File / Document URL', '', 'url'); 657 f += buildLayoutPicker(''); 658 f += buildFilesRepeater([], []); 659 f += fieldHTML('bottom_image', 'Bottom Image URL', '', 'url'); 660 f += '<div class="owfr-news-edit-toggle">' 661 + '<input type="checkbox" name="featured" id="owfr-news-feat-chk" />' 662 + '<label for="owfr-news-feat-chk">â Featured Item</label></div>'; 663 664 var m = createModal('Add New Item', f); 665 initLayoutPicker(m); 666 initFilesRepeater(m); 667 initFrontLogosRepeater(m); 668 669 m.querySelector('.owfr-news-edit-form').addEventListener('submit', function (e) { 670 e.preventDefault(); 671 handleNewItemSave(m, itemId, sectionSlug, grid); 672 }); 673 } 674 675 /* ââââââââââââââââââââââââââââââââââââââ */ 676 /* SAVE / DELETE ITEMS */ 677 /* ââââââââââââââââââââââââââââââââââââââ */ 678 function gatherItemFormData(modal) { 679 var fd = new FormData(modal.querySelector('.owfr-news-edit-form')); 680 var data = {}; 681 data.title = fd.get('title') || ''; 682 data.summary = fd.get('summary') || ''; 683 data.section = fd.get('section') || ''; 684 data.image = fd.get('image') || ''; 685 data.event_date = fd.get('event_date') || ''; 686 data.source_label = fd.get('source_label') || ''; 687 data.language = fd.get('language') || ''; 688 data.external_link = fd.get('external_link') || ''; 689 data.button_text = fd.get('button_text') || 'Learn More'; 690 data.file_url = fd.get('file_url') || ''; 691 data.bottom_image = fd.get('bottom_image') || ''; 692 data.featured = !!fd.get('featured'); 693 data.status = 'active'; 694 /* Layout */ 695 var layout = fd.get('layout'); 696 data._layout = (layout !== null) ? layout : ''; 697 /* Gather EN files */ 698 data.files_en = []; 699 var enList = modal.querySelector('.owfr-news-files-list[data-lang="en"]'); 700 if (enList) { 701 enList.querySelectorAll('.owfr-news-file-row').forEach(function (row) { 702 var url = row.querySelector('.owfr-news-file-row-url').value.trim(); 703 var label = row.querySelector('.owfr-news-file-row-label').value.trim(); 704 if (url) data.files_en.push({ url: url, label: label }); 705 }); 706 } 707 /* Gather FR files */ 708 data.files_fr = []; 709 var frList = modal.querySelector('.owfr-news-files-list[data-lang="fr"]'); 710 if (frList) { 711 frList.querySelectorAll('.owfr-news-file-row').forEach(function (row) { 712 var url = row.querySelector('.owfr-news-file-row-url').value.trim(); 713 var label = row.querySelector('.owfr-news-file-row-label').value.trim(); 714 if (url) data.files_fr.push({ url: url, label: label }); 715 }); 716 } 717 /* Gather partner logos */ 718 data.logos = []; 719 modal.querySelectorAll('.owfr-news-logos-list [name="logos[]"]').forEach(function (inp) { 720 if (inp.value.trim()) data.logos.push(inp.value.trim()); 721 }); 722 return data; 723 } 724 725 function handleItemSave(modal, itemId, card) { 726 var status = modal.querySelector('.owfr-news-edit-status'); 727 if (!config) { status.textContent = 'â Cannot save.'; status.style.color = '#c00'; return; } 728 729 var data = gatherItemFormData(modal); 730 if (!data.title) { status.textContent = 'Title is required.'; status.style.color = '#c00'; return; } 731 732 status.textContent = 'Savingâ¦'; status.style.color = '#666'; 733 fetch(config.restUrl, { 734 method: 'POST', credentials: 'same-origin', 735 headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': config.nonce }, 736 body: JSON.stringify({ itemId: itemId, data: data }) 737 }) 738 .then(function (r) { if (!r.ok) throw new Error(); return r.json(); }) 739 .then(function () { 740 applyItemOverrides(card, data); 741 status.textContent = 'â Saved!'; status.style.color = '#006B3F'; 742 /* Also update matching card in the other panel */ 743 syncDuplicateCards(itemId, data, card); 744 setTimeout(closeModal, 700); 745 }) 746 .catch(function () { status.textContent = 'â Save failed.'; status.style.color = '#c00'; }); 747 } 748 749 function handleNewItemSave(modal, itemId, sectionSlug, grid) { 750 var status = modal.querySelector('.owfr-news-edit-status'); 751 if (!config) { status.textContent = 'â Cannot save.'; status.style.color = '#c00'; return; } 752 753 var data = gatherItemFormData(modal); 754 if (!data.title) { status.textContent = 'Title is required.'; status.style.color = '#c00'; return; } 755 756 status.textContent = 'Savingâ¦'; status.style.color = '#666'; 757 fetch(config.restUrl, { 758 method: 'POST', credentials: 'same-origin', 759 headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': config.nonce }, 760 body: JSON.stringify({ itemId: itemId, data: data }) 761 }) 762 .then(function (r) { if (!r.ok) throw new Error(); return r.json(); }) 763 .then(function () { 764 renderNewCard(itemId, data, grid); 765 status.textContent = 'â Created!'; status.style.color = '#006B3F'; 766 setTimeout(closeModal, 600); 767 }) 768 .catch(function () { status.textContent = 'â Save failed.'; status.style.color = '#c00'; }); 769 } 770 771 function deleteItem(card) { 772 if (!confirm('Permanently delete this item? This cannot be undone.')) return; 773 var itemId = card.dataset.itemId; 774 if (config) { 775 fetch(config.restUrl + '/' + encodeURIComponent(itemId), { 776 method: 'DELETE', credentials: 'same-origin', 777 headers: { 'X-WP-Nonce': config.nonce } 778 }).catch(function () {}); 779 } 780 /* Remove from all panels */ 781 document.querySelectorAll('.owfr-news-card[data-item-id="' + itemId + '"]').forEach(function (c) { c.remove(); }); 782 } 783 784 /* ââââââââââââââââââââââââââââââââââââââ */ 785 /* APPLY OVERRIDES TO EXISTING CARD */ 786 /* ââââââââââââââââââââââââââââââââââââââ */ 787 function applyItemOverrides(card, data) { 788 var el; 789 /* Partner logos take priority over single image */ 790 var hasLogos = data.logos && data.logos.length > 0; 791 var existingLogosDiv = card.querySelector('.owfr-news-card-logos'); 792 var existingImg = card.querySelector('.owfr-news-card-image'); 793 if (hasLogos) { 794 /* Build / rebuild logos div */ 795 var logosDiv = existingLogosDiv || document.createElement('div'); 796 logosDiv.className = 'owfr-news-card-logos'; 797 logosDiv.innerHTML = ''; 798 data.logos.forEach(function (url) { 799 var li = document.createElement('img'); 800 li.className = 'owfr-news-logo-img'; li.src = url; li.alt = ''; li.loading = 'lazy'; 801 logosDiv.appendChild(li); 802 }); 803 if (!existingLogosDiv) card.insertBefore(logosDiv, card.firstChild); 804 /* Remove single image if logos are present */ 805 if (existingImg) existingImg.remove(); 806 } else { 807 /* No logos â remove logos div if present, handle single image */ 808 if (existingLogosDiv) existingLogosDiv.remove(); 809 if (data.image) { 810 if (existingImg) { existingImg.src = data.image; } 811 else { 812 var img = document.createElement('img'); 813 img.className = 'owfr-news-card-image'; img.src = data.image; img.alt = ''; img.loading = 'lazy'; 814 card.insertBefore(img, card.firstChild); 815 } 816 } 817 } 818 el = card.querySelector('h3'); if (el && data.title) el.textContent = data.title; 819 el = card.querySelector('.owfr-news-card-summary'); 820 if (el && data.summary !== undefined) el.textContent = data.summary; 821 822 /* featured */ 823 if (data.featured) { card.classList.add('owfr-news-card-featured'); } 824 else { card.classList.remove('owfr-news-card-featured'); } 825 826 /* date */ 827 el = card.queryS
827elector('.owfr-news-date'); 828 if (data.event_date && el) el.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="14" height="14"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg> ' + esc(data.event_date); 829 830 /* source */ 831 el = card.querySelector('.owfr-news-source'); 832 if (el && data.source_label !== undefined) el.textContent = data.source_label; 833 834 /* language */ 835 el = card.querySelector('.owfr-news-lang'); 836 if (el && data.language !== undefined) el.textContent = data.language; 837 838 /* buttons */ 839 el = card.querySelector('.owfr-news-btn-primary'); 840 if (el) { if (data.external_link) { el.href = data.external_link; el.textContent = data.button_text || 'Learn More'; } } 841 el = card.querySelector('.owfr-news-btn-secondary'); 842 if (el) { if (data.file_url) el.href = data.file_url; } 843 844 /* multiple files â EN/FR groups */ 845 var filesWrap = card.querySelector('.owfr-news-card-files'); 846 var hasEn = data.files_en && data.files_en.length; 847 var hasFr = data.files_fr && data.files_fr.length; 848 if (hasEn || hasFr) { 849 if (!filesWrap) { 850 filesWrap = document.createElement('div'); 851 filesWrap.className = 'owfr-news-card-files'; 852 var actions = card.querySelector('.owfr-news-card-actions'); 853 if (actions) actions.appendChild(filesWrap); 854 } 855 filesWrap.innerHTML = ''; 856 if (hasEn) { 857 var enGroup = document.createElement('div'); 858 enGroup.className = 'owfr-news-files-lang-group'; 859 enGroup.innerHTML = '<div class="owfr-news-files-lang-header"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="14" height="14"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg> English</div>'; 860 data.files_en.forEach(function (fi) { 861 if (!fi.url) return; 862 var a = document.createElement('a'); 863 a.className = 'owfr-news-file-link'; a.href = fi.url; a.target = '_blank'; a.rel = 'noopener'; a.dataset.lang = 'en'; 864 a.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="15" height="15"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg> ' + esc(fi.label || fi.url.split('/').pop()); 865 enGroup.appendChild(a); 866 }); 867 filesWrap.appendChild(enGroup); 868 } 869 if (hasFr) { 870 var frGroup = document.createElement('div'); 871 frGroup.className = 'owfr-news-files-lang-group'; 872 frGroup.innerHTML = '<div class="owfr-news-files-lang-header"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="14" height="14"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg> Français</div>'; 873 data.files_fr.forEach(function (fi) { 874 if (!fi.url) return; 875 var a = document.createElement('a'); 876 a.className = 'owfr-news-file-link'; a.href = fi.url; a.target = '_blank'; a.rel = 'noopener'; a.dataset.lang = 'fr'; 877 a.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="15" height="15"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg> ' + esc(fi.label || fi.url.split('/').pop()); 878 frGroup.appendChild(a); 879 }); 880 filesWrap.appendChild(frGroup); 881 } 882 } else if (filesWrap) { filesWrap.remove(); } 883 884 /* bottom image */ 885 var bottomImg = card.querySelector('.owfr-news-card-bottom-image'); 886 if (data.bottom_image) { 887 if (bottomImg) { bottomImg.src = data.bottom_image; } 888 else { 889 bottomImg = document.createElement('img'); 890 bottomImg.className = 'owfr-news-card-bottom-image'; bottomImg.src = data.bottom_image; bottomImg.alt = ''; bottomImg.loading = 'lazy'; 891 var actionsEl = card.querySelector('.owfr-news-card-actions'); 892 if (actionsEl) actionsEl.appendChild(bottomImg); 893 } 894 } else if (bottomImg) { bottomImg.remove(); } 895 896 /* layout */ 897 if (typeof data._layout === 'string') { 898 card.classList.remove('owfr-news-card-wide', 'owfr-news-card-half'); 899 if (data._layout) card.classList.add(data._layout); 900 } 901 902 /* section data attribute */ 903 if (data.section) card.dataset.section = data.section; 904 905 /* badges */ 906 var badgeWrap = card.querySelector('.owfr-news-card-meta-top'); 907 if (badgeWrap) { 908 var sectionBadge = badgeWrap.querySelector('.owfr-news-badge:not(.owfr-news-badge-featured)'); 909 if (sectionBadge && data.section) { 910 var tab = document.querySelector('.owfr-news-tab-section[data-section="' + data.section + '"]'); 911 if (tab) sectionBadge.textContent = tab.textContent.replace(/Ã$/, '').trim(); 912 } 913 var featBadge = badgeWrap.querySelector('.owfr-news-badge-featured'); 914 if (data.featured && !featBadge) { 915 var fb = document.createElement('span'); 916 fb.className = 'owfr-news-badge owfr-news-badge-featured'; 917 fb.textContent = 'â Featured'; 918 badgeWrap.appendChild(fb); 919 } else if (!data.featured && featBadge) { 920 featBadge.remove(); 921 } 922 } 923 } 924 925 function syncDuplicateCards(itemId, data, sourceCard) {
926 /* Update any matching cards in other panels */ 927 document.querySelectorAll('.owfr-news-card[data-item-id="' + itemId + '"]').forEach(function (c) { 928 if (c !== sourceCard) applyItemOverrides(c, data); 929 }); 930 } 931 932 /* ââââââââââââââââââââââââââââââââââââââ */ 933 /* RENDER NEW CARD IN DOM */ 934 /* ââââââââââââââââââââââââââââââââââââââ */ 935 function renderNewCard(itemId, data, grid) { 936 if (grid.querySelector('[data-item-id="' + itemId + '"]')) return; 937 938 var sectionName = ''; 939 var tab = document.querySelector('.owfr-news-tab-section[data-section="' + (data.section || '') + '"]'); 940 if (tab) sectionName = tab.textContent.replace(/Ã$/, '').trim(); 941 942 var card = document.createElement('article'); 943 card.className = 'owfr-news-card' + (data.featured ? ' owfr-news-card-featured' : '') + (data._layout ? ' ' + data._layout : ''); 944 card.dataset.itemId = itemId; 945 card.dataset.section = data.section || ''; 946 card.style.position = 'relative'; 947 948 var html = ''; 949 var hasLogos2 = data.logos && data.logos.length > 0; 950 if (hasLogos2) { 951 html += '<div class="owfr-news-card-logos">'; 952 data.logos.forEach(function (url) { 953 html += '<img src="' + esc(url) + '" alt="" class="owfr-news-logo-img" loading="lazy" />'; 954 }); 955 html += '</div>'; 956 } else if (data.image) { 957 html += '<img src="' + esc(data.image) + '" alt="" class="owfr-news-card-image" loading="lazy" />'; 958 } 959 html += '<div class="owfr-news-card-content">'; 960 html += '<div class="owfr-news-card-meta-top">'; 961 if (sectionName) html += '<span class="owfr-news-badge">' + esc(sectionName) + '</span>'; 962 if (data.featured) html += '<span class="owfr-news-badge owfr-news-badge-featured">â Featured</span>'; 963 html += '</div>'; 964 html += '<h3>' + esc(data.title) + '</h3>'; 965 if (data.summary) html += '<p class="owfr-news-card-summary">' + esc(data.summary) + '</p><button class="owfr-news-read-more" aria-expanded="false">Read more</button>'; 966 html += '<div class="owfr-news-card-meta-bottom">'; 967 if (data.event_date) html += '<span class="owfr-news-date"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="14" height="14"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg> ' + esc(data.event_date) + '</span>'; 968 if (data.source_label) html += '<span class="owfr-news-source">' + esc(data.source_label) + '</span>'; 969 if (data.language) html += '<span class="owfr-news-lang">' + esc(data.language) + '</span>'; 970 html += '</div>'; 971 html += '<div class="owfr-news-card-actions">'; 972 if (data.external_link) html += '<a class="owfr-news-btn owfr-news-btn-primary" href="' + esc(data.external_link) + '" target="_blank" rel="noopener">' + esc(data.button_text || 'Learn More') + '</a>'; 973 if (data.file_url) html += '<a class="owfr-news-btn owfr-news-btn-secondary" href="' + esc(data.file_url) + '" target="_blank" rel="noopener">ð Download</a>'; 974 var hasEn2 = data.files_en && data.files_en.length; 975 var hasFr2 = data.files_fr && data.files_fr.length; 976 if (hasEn2 || hasFr2) { 977 html += '<div class="owfr-news-card-files">'; 978 if (hasEn2) { 979 html += '<div class="owfr-news-files-lang-group"><div class="owfr-news-files-lang-header">' 980 + '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="14" height="14"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>' 981 + ' English</div>'; 982 data.files_en.forEach(function (fi) { 983 if (!fi.url) return; 984 html += '<a class="owfr-news-file-link" href="' + esc(fi.url) + '" target="_blank" rel="noopener" data-lang="en">' 985 + '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="15" height="15"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg> ' 986 + esc(fi.label || fi.url.split('/').pop()) + '</a>'; 987 }); 988 html += '</div>'; 989 } 990 if (hasFr2) { 991 html += '<div class="owfr-news-files-lang-group"><div class="owfr-news-files-lang-header">' 992 + '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="14" height="14"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>' 993 + ' Fran\u00e7ais</div>'; 994 data.files_fr.forEach(function (fi) { 995 if (!fi.url) return; 996 html += '<a class="owfr-news-file-link" href="' + esc(fi.url) + '" target="_blank" rel="noopener" data-lang="fr">' 997 + '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="15" height="15"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg> ' 998 + esc(fi.label || fi.url.split('/').pop()) + '</a>'; 999 }); 1000 html += '</div>'; 1001 } 1002 html += '</div>'; 1003 } 1004 if (data.bottom_image) html += '<img src="' + esc(data.bottom_image) + '" alt="" class="owfr-news-card-bottom-image" loading="lazy" />'; 1005 html += '</div></div>'; 1006 1007 card.innerHTML = html; 1008 1009 var addBtn = grid.querySelector('.owfr-news-admin-add-card'); 1010 if (addBtn) grid.insertBefore(card, addBtn); 1011 else grid.appendChild(card); 1012 1013 requestAnimationFrame(function () { initReadMoreBtns(card); }); 1014 injectCardAdminButtons(card); 1015 } 1016 1017 /* ââââââââââââââââââââââââââââââââââââââ */ 1018 /* MODAL: EDIT HERO */ 1019 /* ââââââââââââââââââââââââââââââââââââââ */ 1020 function openHeroEditor(heroEl) { 1021 var h2 = heroEl.querySelector('h2'); 1022 var p = heroEl.querySelector('p'); 1023 var curTitle = h2 ? h2.textContent : ''; 1024 var curIntro = p ? p.innerHTML : ''; 1025 1026 var f = ''; 1027 f += fieldHTML('hero_title', 'Hero Title', curTitle, 'text'); 1028 f += '<div class="owfr-news-edit-field"><label>Intro Text <span class="owfr-news-label-hint">(HTML ok)</span></label>' 1029 + '<textarea name="hero_intro" rows="3">' + esc(curIntro) + '</textarea></div>'; 1030 1031 var m = createModal('Edit Hero Section', f); 1032 1033 m.querySelector('.owfr-news-edit-form').addEventListener('submit', function (e) { 1034 e.preventDefault(); 1035 var status = m.querySelector('.owfr-news-edit-status'); 1036 if (!settingsConfig) { status.textContent = 'â Cannot save.'; status.style.color = '#c00'; return; } 1037 1038 var fd = new FormData(m.querySelector('.owfr-news-edit-form')); 1039 var payload = { 1040 hero_title: fd.get('hero_title') || '', 1041 hero_intro: fd.get('hero_intro') || '' 1042 }; 1043 1044 status.textContent = 'Savingâ¦'; status.style.color = '#666'; 1045 fetch(settingsConfig.restUrl, { 1046 method: 'POST', credentials: 'same-origin', 1047 headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': settingsConfig.nonce }, 1048 body: JSON.stringify(payload) 1049 }) 1050 .then(function (r) { if (!r.ok) throw new Error(); return r.json(); }) 1051 .then(function () { 1052 if (h2) h2.textContent = payload.hero_title; 1053 if (p) p.innerHTML = payload.hero_intro; 1054 status.textContent = 'â Saved!'; status.style.color = '#006B3F'; 1055 setTimeout(closeModal, 700); 1056 }) 1057 .catch(function () { status.textContent = 'â Save failed.'; status.style.color = '#c00'; }); 1058 }); 1059 } 1060 1061 /* ââââââââââââââââââââââââââââââââââââââ */ 1062 /* MODAL: EDIT WELCOME BANNER */ 1063 /* ââââââââââââââââââââââââââââââââââââââ */ 1064 function openWelcomeEditor(welEl) { 1065 var titleEl = welEl.querySelector('.owfr-news-welcome-title'); 1066 var textEl = welEl.querySelector('.owfr-news-welcome-text'); 1067 var steps = welEl.querySelectorAll('.owfr-news-welcome-step-label'); 1068 1069 var curTitle = titleEl ? titleEl.textContent : ''; 1070 var curText = textEl ? textEl.innerHTML : ''; 1071 var curStep1 = steps[0] ? steps[0].innerHTML : ''; 1072 var curStep2 = steps[1] ? steps[1].innerHTML : ''; 1073 var curStep3 = steps[2] ? steps[2].innerHTML : ''; 1074 1075 var f = ''; 1076 f += fieldHTML('welcome_title', 'Banner Title', curTitle, 'text'); 1077 f += '<div class="owfr-news-edit-field"><label>Description <span class="owfr-news-label-hint">(HTML ok)</span></label>' 1078 + '<textarea name="welcome_text" rows="3">' + esc(curText) + '</textarea></div>'; 1079 f += '<div class="owfr-news-edit-field"><label>Step 1 <span class="owfr-news-label-hint">(HTML ok)</span></label>' 1080 + '<input type="text" name="welcome_step_1" value="' + esc(curStep1) + '" /></div>'; 1081 f += '<div class="owfr-news-edit-field"><label>Step 2 <span class="owfr-news-label-hint">(HTML ok)</span></label>' 1082 + '<input type="text" name="welcome_step_2" value="' + esc(curStep2) + '" /></div>'; 1083 f += '<div class="owfr-news-edit-field"><label>Step 3 <span class="owfr-news-label-hint">(HTML ok)</span></label>' 1084 + '<input type="text" name="welcome_step_3" value="' + esc(curStep3) + '" /></div>'; 1085 1086 var m = createModal('Edit Welcome Banner', f); 1087 1088 m.querySelector('.owfr-news-edit-form').addEventListener('submit', function (e) { 1089 e.preventDefault(); 1090 var status = m.querySelector('.owfr-news-edit-status'); 1091 if (!settingsConfig) { status.textContent = 'â Cannot save.'; status.style.color = '#c00'; return; } 1092 1093 var fd = new FormData(m.querySelector('.owfr-news-edit-form')); 1094 var payload = { 1095 welcome_title: fd.get('welcome_title') || '', 1096 welcome_text: fd.get('welcome_text') || '', 1097 welcome_step_1: fd.get('welcome_step_1') || '', 1098 welcome_step_2: fd.get('welcome_step_2') || '', 1099 welcome_step_3: fd.get('welcome_step_3') || '' 1100 }; 1101 1102 status.textContent = 'Savingâ¦'; status.style.color = '#666'; 1103 fetch(settingsConfig.restUrl, { 1104 method: 'POST', credentials: 'same-origin', 1105 headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': settingsConfig.nonce }, 1106 body: JSON.stringify(payload) 1107 }) 1108 .then(function (r) { if (!r.ok) throw new Error(); return r.json(); }) 1109 .then(function () { 1110 if (titleEl) titleEl.textContent = payload.welcome_title; 1111 if (textEl) textEl.innerHTML = payload.welcome_text; 1112 if (steps[0]) steps[0].innerHTML = payload.welcome_step_1; 1113 if (steps[1]) steps[1].innerHTML = payload.welcome_step_2; 1114 if (steps[2]) steps[2].innerHTML = payload.welcome_step_3; 1115 status.textContent = 'â Saved!'; status.style.color = '#006B3F'; 1116 setTimeout(closeModal, 700); 1117 }) 1118 .catch(function () { status.textContent = 'â Save failed.'; status.style.color = '#c00'; }); 1119 }); 1120 } 1121 1122 /* ââââââââââââââââââââââââââââââââââââââ */ 1123 /* MODAL: EDIT / ADD SECTION */ 1124 /* ââââââââââââââââââââââââââââââââââââââ */ 1125 function openSectionEditor(hdr) { 1126 var slug = hdr.dataset.sectionSlug; 1127 var titleEl = hdr.querySelector('.owfr-news-section-title'); 1128 var curName = titleEl ? titleEl.textContent.trim() : ''; 1129 1130 var f = ''; 1131 f += fieldHTML('name', 'Section Name', curName, 'text'); 1132 f += '<input type="hidden" name="slug" value="' + esc(slug) + '" />'; 1133 1134 var m = createModal('Edit Section', f); 1135 m.querySelector('.owfr-news-edit-form').addEventListener('submit', function (e) { 1136 e.preventDefault(); 1137 var status = m.querySelector('.owfr-news-edit-status'); 1138 if (!sectionsConfig) { status.textContent = 'â Cannot save.'; status.style.color = '#c00'; return; } 1139 1140 var fd = new FormData(m.querySelector('.owfr-news-edit-form')); 1141 var name = (fd.get('name') || '').trim(); 1142 if (!name) { status.textContent = 'Section name is required.'; status.style.color = '#c00'; return; } 1143 1144 status.textContent = 'Savingâ¦'; status.style.color = '#666'; 1145 fetch(sectionsConfig.restUrl, { 1146 method: 'POST', credentials: 'same-origin', 1147 headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': sectionsConfig.nonce }, 1148 body: JSON.stringify({ slug: slug, name: name, icon: '' }) 1149 }) 1150 .then(function (r) { if (!r.ok) throw new Error(); return r.json(); }) 1151 .then(function () { 1152 if (titleEl) titleEl.textContent = name;
1153 /* Update the tab button text */ 1154 var tab = document.querySelector('.owfr-news-tab-section[data-section="' + slug + '"]'); 1155 if (tab) { 1156 var deleteSpan = tab.querySelector('.owfr-news-admin-delete-section-tab'); 1157 tab.textContent = name; 1158 if (deleteSpan) tab.appendChild(deleteSpan); 1159 } 1160 status.textContent = 'â Saved!'; status.style.color = '#006B3F'; 1161 setTimeout(closeModal, 600); 1162 }) 1163 .catch(function () { status.textContent = 'â Save failed.'; status.style.color = '#c00'; }); 1164 }); 1165 } 1166 1167 function openNewSectionEditor() { 1168 var f = ''; 1169 f += fieldHTML('name', 'Section Name', '', 'text'); 1170 1171 var m = createModal('Add New Section', f); 1172 m.querySelector('.owfr-news-edit-form').addEventListener('submit', function (e) { 1173 e.preventDefault(); 1174 var status = m.querySelector('.owfr-news-edit-status'); 1175 if (!sectionsConfig) { status.textContent = 'â Cannot save.'; status.style.color = '#c00'; return; } 1176 1177 var fd = new FormData(m.querySelector('.owfr-news-edit-form')); 1178 var name = (fd.get('name') || '').trim(); 1179 if (!name) { status.textContent = 'Section name is required.'; status.style.color = '#c00'; return; } 1180 1181 var slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); 1182 if (!slug) { status.textContent = 'Invalid name.'; status.style.color = '#c00'; return; } 1183 1184 /* check for duplicate */ 1185 if (document.querySelector('.owfr-news-tab-section[data-section="' + slug + '"]')) { 1186 status.textContent = 'A section with this name already exists.'; status.style.color = '#c00'; return; 1187 } 1188 1189 status.textContent = 'Savingâ¦'; status.style.color = '#666'; 1190 fetch(sectionsConfig.restUrl, { 1191 method: 'POST', credentials: 'same-origin', 1192 headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': sectionsConfig.nonce }, 1193 body: JSON.stringify({ slug: slug, name: name, icon: '' }) 1194 }) 1195 .then(function (r) { if (!r.ok) throw new Error(); return r.json(); }) 1196 .then(function () { 1197 renderNewSection(slug, name); 1198 status.textContent = 'â Section created!'; status.style.color = '#006B3F'; 1199 setTimeout(closeModal, 600); 1200 }) 1201 .catch(function () { status.textContent = 'â Save failed.'; status.style.color = '#c00'; }); 1202 }); 1203 } 1204 1205 function renderNewSection(slug, name) { 1206 /* Add tab */ 1207 var tl = document.getElementById('owfr-news-tabs'); 1208 if (!tl) return; 1209 var li = document.createElement('li'); 1210 var btn = document.createElement('button'); 1211 btn.className = 'owfr-news-tab owfr-news-tab-section'; 1212 btn.dataset.section = slug; 1213 btn.setAttribute('role', 'tab'); 1214 btn.type = 'button'; 1215 btn.textContent = name; 1216 li.appendChild(btn); 1217 var addLi = tl.querySelector('.owfr-news-add-section-li'); 1218 if (addLi) tl.insertBefore(li, addLi); 1219 else tl.appendChild(li); 1220 injectTabDeleteButton(btn); 1221 1222 /* Add panel */ 1223 var inner = document.querySelector('.owfr-news-inner'); 1224 if (!inner) return; 1225 var panel = document.createElement('div'); 1226 panel.className = 'owfr-news-panel'; 1227 panel.id = 'owfr-news-panel-' + slug; 1228 panel.setAttribute('role', 'tabpanel'); 1229 panel.innerHTML = '<div class="owfr-news-section-header" data-section-slug="' + esc(slug) + '">' 1230 + '<span class="owfr-news-section-title">' + esc(name) + '</span>' 1231 + '<span class="owfr-news-section-count">0 items</span>' 1232 + '</div><div class="owfr-news-grid"></div>'; 1233 inner.appendChild(panel); 1234 1235 /* Section header admin buttons */ 1236 var hdr = panel.querySelector('.owfr-news-section-header'); 1237 if (hdr) { 1238 hdr.style.position = 'relative'; 1239 var editBtn = document.createElement('button'); 1240 editBtn.className = 'owfr-news-admin-section-edit-btn'; 1241 editBtn.type = 'button'; 1242 editBtn.textContent = 'â Edit'; 1243 editBtn.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); openSectionEditor(hdr); }); 1244 hdr.appendChild(editBtn); 1245 1246 var delBtn = document.createElement('button'); 1247 delBtn.className = 'owfr-news-admin-section-delete-btn'; 1248 delBtn.type = 'button'; 1249 delBtn.textContent = 'â Delete'; 1250 delBtn.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); deleteSection(slug); }); 1251 hdr.appendChild(delBtn); 1252 } 1253 1254 /* "Add New" button in the grid */ 1255 var grid = panel.querySelector('.owfr-news-grid'); 1256 var addCard = document.createElement('button'); 1257 addCard.className = 'owfr-news-admin-add-card'; 1258 addCard.type = 'button'; 1259 addCard.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg><span>Add New Item</span>'; 1260 addCard.addEventListener('click', function () { openNewItemEditor(slug, grid); }); 1261 grid.appendChild(addCard); 1262 1263 initGridDragDrop(grid, slug); 1264 1265 /* Switch to the new tab */ 1266 btn.click(); 1267 } 1268 1269 function deleteSection(slug) { 1270 if (!confirm('Delete this section? Items in this section will NOT be deleted, but they will become uncategorized.')) return; 1271 if (sectionsConfig) { 1272 fetch(sectionsConfig.restUrl + '/' + encodeURIComponent(slug), {
1273 method: 'DELETE', credentials: 'same-origin', 1274 headers: { 'X-WP-Nonce': sectionsConfig.nonce } 1275 }).catch(function () {}); 1276 } 1277 /* Remove tab */ 1278 var tab = document.querySelector('.owfr-news-tab-section[data-section="' + slug + '"]'); 1279 if (tab && tab.parentElement) tab.parentElement.remove(); 1280 /* Remove panel */ 1281 var panel = document.getElementById('owfr-news-panel-' + slug); 1282 if (panel) panel.remove(); 1283 /* Switch to first tab */ 1284 var first = document.querySelector('.owfr-news-tab'); 1285 if (first) first.click(); 1286 } 1287 1288 /* ââââââââââââââââââââââââââââââââââââââ */ 1289 /* DRAG-AND-DROP REORDER */ 1290 /* ââââââââââââââââââââââââââââââââââââââ */ 1291 var draggedCard = null; 1292 var placeholder = null; 1293 var dirtyPanels = {}; 1294 1295 function injectDragHandle(card) { 1296 if (card.querySelector('.owfr-news-drag-handle')) return; 1297 card.setAttribute('draggable', 'true'); 1298 card.style.position = 'relative'; 1299 var handle = document.createElement('button'); 1300 handle.className = 'owfr-news-drag-handle'; 1301 handle.type = 'button'; 1302 handle.title = 'Drag to reorder'; 1303 handle.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' 1304 + '<circle cx="9" cy="6" r="1"/><circle cx="15" cy="6" r="1"/>' 1305 + '<circle cx="9" cy="12" r="1"/><circle cx="15" cy="12" r="1"/>' 1306 + '<circle cx="9" cy="18" r="1"/><circle cx="15" cy="18" r="1"/></svg>'; 1307 card.insertBefore(handle, card.firstChild); 1308 card.addEventListener('dragstart', onDragStart); 1309 card.addEventListener('dragend', onDragEnd); 1310 } 1311 1312 function onDragStart(e) { 1313 draggedCard = this; 1314 this.classList.add('owfr-news-dragging'); 1315 placeholder = document.createElement('div'); 1316 placeholder.className = 'owfr-news-drop-indicator'; 1317 e.dataTransfer.effectAllowed = 'move'; 1318 e.dataTransfer.setData('text/plain', this.dataset.itemId); 1319 var grid = this.closest('.owfr-news-grid'); 1320 if (grid) grid.classList.add('owfr-news-drag-active'); 1321 } 1322 1323 function onDragEnd() { 1324 if (draggedCard) draggedCard.classList.remove('owfr-news-dragging'); 1325 if (placeholder && placeholder.parentNode) placeholder.parentNode.removeChild(placeholder); 1326 document.querySelectorAll('.owfr-news-grid.owfr-news-drag-active').forEach(function (g) { g.classList.remove('owfr-news-drag-active'); }); 1327 draggedCard = null; 1328 placeholder = null; 1329 } 1330 1331 function initGridDragDrop(grid, sectionId) { 1332 grid.addEventListener('dragover', function (e) { 1333 e.preventDefault(); e.dataTransfer.dropEffect = 'move'; 1334 if (!draggedCard) return; 1335 var t = getDropTarget(e, grid); 1336 if (t && t !== draggedCard && t !== placeholder) { 1337 var r = t.getBoundingClientRect(); 1338 if (e.clientY < r.top + r.height / 2) grid.insertBefore(placeholder, t); 1339 else grid.insertBefore(placeholder, t.nextSibling); 1340 } else if (!grid.contains(placeholder)) { 1341 var ab = grid.querySelector('.owfr-news-admin-add-card'); 1342 if (ab) grid.insertBefore(placeholder, ab); 1343 else grid.appendChild(placeholder); 1344 } 1345 }); 1346 grid.addEventListener('drop', function (e) { 1347 e.preventDefault(); 1348 if (!draggedCard || !placeholder || !placeholder.parentNode) return; 1349 grid.insertBefore(draggedCard, placeholder); 1350 placeholder.parentNode.removeChild(placeholder); 1351 draggedCard.classList.remove('owfr-news-dragging'); 1352 dirtyPanels[sectionId] = true; 1353 showSaveOrderBtn(); 1354 draggedCard = null; 1355 placeholder = null; 1356 grid.classList.remove('owfr-news-drag-active'); 1357 }); 1358 } 1359 1360 function getDropTarget(e, grid) { 1361 var cards = grid.querySelectorAll('.owfr-news-card[data-item-id]'); 1362 var closest = null, closestDist = Infinity; 1363 cards.forEach(function (card) { 1364 if (card === draggedCard) return; 1365 var rect = card.getBoundingClientRect(); 1366 var midY = rect.top + rect.height / 2; 1367 var dist = Math.abs(e.clientY - midY); 1368 if (dist < closestDist) { closestDist = dist; closest = card; } 1369 }); 1370 return closest; 1371 } 1372 1373 /* Init drag-drop on existing section grids (skip "all" panel) */ 1374 document.querySelectorAll('.owfr-news-panel').forEach(function (panel) { 1375 var sectionId = panel.id.replace('owfr-news-panel-', ''); 1376 if (sectionId === 'all') return; 1377 var grid = panel.querySelector('.owfr-news-grid'); 1378 if (grid) initGridDragDrop(grid, sectionId); 1379 }); 1380
1381 /* Save Order floating button */ 1382 var saveOrderBtn = document.createElement('button'); 1383 saveOrderBtn.className = 'owfr-news-save-order-btn'; 1384 saveOrderBtn.type = 'button'; 1385 saveOrderBtn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg> Save Order'; 1386 document.body.appendChild(saveOrderBtn); 1387 1388 function showSaveOrderBtn() { saveOrderBtn.classList.add('visible'); } 1389 1390 saveOrderBtn.addEventListener('click', function () { 1391 if (!orderConfig) { alert('Order endpoint not configured.'); return; } 1392 saveOrderBtn.textContent = 'Savingâ¦'; 1393 saveOrderBtn.disabled = true; 1394 1395 var sections = Object.keys(dirtyPanels); 1396 var promises = sections.map(function (sectionId) { 1397 var panel = document.getElementById('owfr-news-panel-' + sectionId); 1398 if (!panel) return Promise.resolve(); 1399 var grid = panel.querySelector('.owfr-news-grid'); 1400 if (!grid) return Promise.resolve(); 1401 var order = []; 1402 grid.querySelectorAll('.owfr-news-card[data-item-id]').forEach(function (c) { order.push(c.dataset.itemId); }); 1403 return fetch(orderConfig.reorderUrl, { 1404 method: 'POST', credentials: 'same-origin', 1405 headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': orderConfig.nonce }, 1406 body: JSON.stringify({ section: sectionId, order: order }) 1407 }); 1408 }); 1409 1410 Promise.all(promises).then(function () { 1411 saveOrderBtn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> Saved!'; 1412 dirtyPanels = {}; 1413 setTimeout(function () { 1414 saveOrderBtn.classList.remove('visible'); 1415 saveOrderBtn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg> Save Order'; 1416 saveOrderBtn.disabled = false; 1417 }, 1500); 1418 }).catch(function () { 1419 saveOrderBtn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg> Save Order'; 1420 saveOrderBtn.disabled = false; 1421 alert('Failed to save order.'); 1422 }); 1423 }); 1424 1425 /* ââââââââââââââââââââââââââââââââââââââ */ 1426 /* IMAGE LIGHTBOX */ 1427 /* ââââââââââââââââââââââââââââââââââââââ */ 1428 function openLightbox(src) { 1429 closeLightbox(); 1430 var lb = document.createElement('div'); 1431 lb.className = 'owfr-news-lightbox'; 1432 lb.innerHTML = '<button class="owfr-news-lightbox-close" aria-label="Close">×</button>' 1433 + '<img src="' + esc(src) + '" alt="" />'; 1434 document.body.appendChild(lb); 1435 document.body.style.overflow = 'hidden'; 1436 lb.addEventListener('click', function (e) { 1437 if (e.target === lb) closeLightbox(); 1438 }); 1439 lb.querySelector('.owfr-news-lightbox-close').addEventListener('click', closeLightbox); 1440 } 1441 1442 function closeLightbox() { 1443 var lb = document.querySelector('.owfr-news-lightbox'); 1444 if (lb) lb.remove(); 1445 if (!document.getElementById('owfr-news-edit-modal')) { 1446 document.body.style.overflow = ''; 1447 } 1448 } 1449 1450 document.addEventListener('keydown', function (e) { 1451 if (e.key === 'Escape') closeLightbox(); 1452 }); 1453 1454 /* Delegate click on card images */ 1455 document.addEventListener('click', function (e) { 1456 var img = e.target; 1457 if (img.classList.contains('owfr-news-card-image') || img.classList.contains('owfr-news-card-bottom-image')) { 1458 e.preventDefault(); 1459 openLightbox(img.src); 1460 } 1461 }); 1462 1463});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.