| 1 |
/** |
| 2 |
* PowerPress Network Admin |
| 3 |
* |
| 4 |
* TODO: convert var -> const/let |
| 5 |
* |
| 6 |
* SECTIONS: |
| 7 |
* LOCALIZATION |
| 8 |
* DIALOG MODAL |
| 9 |
* PAGE TYPE CONFIG |
| 10 |
* FORM SUBMISSION |
| 11 |
* TAB NAVIGATION |
| 12 |
* PAGE HANDLING |
| 13 |
* LIST MANAGEMENT |
| 14 |
* SEARCH / FILTER |
| 15 |
* APPLICATION APPROVAL |
| 16 |
* PROGRAM MANAGEMENT |
| 17 |
* SETTINGS |
| 18 |
* INIT |
| 19 |
* EVENT DELEGATION |
| 20 |
*/ |
| 21 |
import { ppCopyText, showToast, initCharCounter } from './utils/dom-utils.js'; |
| 22 |
import { escapeHtml } from './utils/sanitize.js'; |
| 23 |
|
| 24 |
// ============ |
| 25 |
// LOCALIZATION |
| 26 |
// ============ |
| 27 |
|
| 28 |
// wp.i18n wrapper w/ graceful fallback |
| 29 |
var __ = (typeof wp !== 'undefined' && wp.i18n && wp.i18n.__) |
| 30 |
? wp.i18n.__ |
| 31 |
: function(s) { return s; }; |
| 32 |
|
| 33 |
// DIALOG MODAL |
| 34 |
|
| 35 |
function ppnDialog(el) { |
| 36 |
var dialogId = el.dataset.ppnDialog || el.getAttribute('data-ppn-dialog'); |
| 37 |
if (!dialogId) return; |
| 38 |
var dialog = document.getElementById(dialogId); |
| 39 |
if (dialog && typeof dialog.showModal === 'function') { |
| 40 |
dialog.showModal(); |
| 41 |
} |
| 42 |
} |
| 43 |
|
| 44 |
function ppnDialogClose(el) { |
| 45 |
var dialog = el.closest('dialog'); |
| 46 |
if (dialog) dialog.close(); |
| 47 |
} |
| 48 |
|
| 49 |
|
| 50 |
// ================ |
| 51 |
// PAGE TYPE CONFIG |
| 52 |
// ================ |
| 53 |
|
| 54 |
/** @type {Object<string, {shortcode: function, defaultTitle: function, hasTargetId: boolean}>} */ |
| 55 |
var PAGE_TYPES = { |
| 56 |
Program: { |
| 57 |
shortcode: function(id) { return `[ppn-program id = ${id}]`; }, |
| 58 |
defaultTitle: function(id) { return `program id = ${id}`; }, |
| 59 |
hasTargetId: true |
| 60 |
}, |
| 61 |
List: { |
| 62 |
shortcode: function(id) { return `[ppn-gridview id="${id}" rows="100" cols="3"]`; }, |
| 63 |
defaultTitle: function(id) { return `list id = ${id}`; }, |
| 64 |
hasTargetId: true |
| 65 |
}, |
| 66 |
Application: { |
| 67 |
shortcode: function() { return '[ppn-application]'; }, |
| 68 |
defaultTitle: function() { return 'Application Page'; }, |
| 69 |
hasTargetId: false |
| 70 |
}, |
| 71 |
Homepage: { |
| 72 |
shortcode: function() { return '[ppn-gridview id="all" rows="100" cols="3"]\n\n[ppn-list id="all" style="detailed"]'; }, |
| 73 |
defaultTitle: function() { return 'Network Page' }, |
| 74 |
hasTargetId: false |
| 75 |
} |
| 76 |
}; |
| 77 |
|
| 78 |
|
| 79 |
// =============== |
| 80 |
// FORM SUBMISSION |
| 81 |
// =============== |
| 82 |
|
| 83 |
/** post formdata to current page, returns true on success */ |
| 84 |
async function _ppnPost(body) { |
| 85 |
try { |
| 86 |
var resp = await fetch(window.location.href, { method: 'POST', body: body }); |
| 87 |
if (!resp.ok) throw new Error('request failed'); |
| 88 |
return true; |
| 89 |
} catch (e) { |
| 90 |
alert(__('Something went wrong. Please refresh and try again.')); |
| 91 |
return false; |
| 92 |
} |
| 93 |
} |
| 94 |
|
| 95 |
function _appendHidden(form, name, value) { |
| 96 |
var input = document.createElement('input'); |
| 97 |
input.type = 'hidden'; |
| 98 |
input.name = name; |
| 99 |
if (value !== undefined) input.value = value; |
| 100 |
form.appendChild(input); |
| 101 |
} |
| 102 |
|
| 103 |
/** generic action: ajax mode (fetch + reload tab) or navigate mode (form submit) */ |
| 104 |
async function _ppnAction(el) { |
| 105 |
// 1) RESOLVE FORM |
| 106 |
var formId = el.dataset.form; |
| 107 |
var form = formId ? document.getElementById(formId) : el.closest('form'); |
| 108 |
if (!form) return; |
| 109 |
|
| 110 |
// 2) CONFIRM |
| 111 |
if (el.hasAttribute('data-confirm')) { |
| 112 |
var msg = el.dataset.confirm || __('Are you sure?'); |
| 113 |
if (!confirm(msg)) return; |
| 114 |
} |
| 115 |
|
| 116 |
// 3) SET FIELD VALUES |
| 117 |
if (el.dataset.setField) { |
| 118 |
var target = document.getElementById(el.dataset.setField); |
| 119 |
if (target) target.value = el.dataset.setValue || ''; |
| 120 |
} |
| 121 |
|
| 122 |
// 4) APPEND DATA-FIELDS ("key:val,key:val") |
| 123 |
if (el.dataset.fields) { |
| 124 |
el.dataset.fields.split(',').forEach(function(pair) { |
| 125 |
var parts = pair.split(':'); |
| 126 |
var existing = form.querySelector('[name="' + parts[0] + '"]'); |
| 127 |
if (existing) { |
| 128 |
existing.value = parts[1] || ''; |
| 129 |
} else { |
| 130 |
_appendHidden(form, parts[0], parts[1] || ''); |
| 131 |
} |
| 132 |
}); |
| 133 |
} |
| 134 |
|
| 135 |
// 5) APPEND FLAGS + NONCE |
| 136 |
if (el.dataset.change === 'true' && !form.querySelector('[name="changeOrCreate"]')) { |
| 137 |
_appendHidden(form, 'changeOrCreate', 'true'); |
| 138 |
} |
| 139 |
if (typeof ppnNonce !== 'undefined' && !form.querySelector('[name="_ppn_nonce"]')) { |
| 140 |
_appendHidden(form, '_ppn_nonce', ppnNonce); |
| 141 |
} |
| 142 |
|
| 143 |
// 6) NAVIGATE MODE: set action url + submit |
| 144 |
if (el.dataset.navigate) { |
| 145 |
var tab = el.dataset.tab ? '&tab=' + el.dataset.tab : ''; |
| 146 |
form.setAttribute('action', `?page=network-plugin&status=${el.dataset.navigate}${tab}`); |
| 147 |
form.submit(); |
| 148 |
return; |
| 149 |
} |
| 150 |
|
| 151 |
// 7) AJAX MODE: post + reload tab |
| 152 |
var ok = await _ppnPost(new FormData(form)); |
| 153 |
if (ok && el.dataset.tab) _reloadTab(el.dataset.tab); |
| 154 |
} |
| 155 |
|
| 156 |
|
| 157 |
// ============== |
| 158 |
// TAB NAVIGATION |
| 159 |
// ============== |
| 160 |
|
| 161 |
function showPPNTab(application) { |
| 162 |
var x = document.getElementsByClassName("tabContent"); |
| 163 |
for (var i = 0; i < x.length; i++) { |
| 164 |
x[i].style.display = "none"; |
| 165 |
} |
| 166 |
var tabContent = document.getElementById(application); |
| 167 |
if (!tabContent) return; |
| 168 |
tabContent.style.display = "block"; |
| 169 |
document.querySelectorAll('.tabActive').forEach(function(el) { |
| 170 |
el.classList.add('tabInactive'); |
| 171 |
el.classList.remove('tabActive'); |
| 172 |
}); |
| 173 |
var tab = document.getElementById(`${application}Tab`); |
| 174 |
if (tab) { |
| 175 |
tab.classList.remove('tabInactive'); |
| 176 |
tab.classList.add('tabActive'); |
| 177 |
} |
| 178 |
} |
| 179 |
|
| 180 |
|
| 181 |
// ============= |
| 182 |
// PAGE HANDLING |
| 183 |
// ============= |
| 184 |
|
| 185 |
/** ajax: link or create a page via ppn_page_action */ |
| 186 |
async function _ppnPageAction(el) { |
| 187 |
const mode = el.dataset.mode; |
| 188 |
const target = el.dataset.target || ''; |
| 189 |
const body = new FormData(); |
| 190 |
body.append('action', 'ppn_page_action'); |
| 191 |
body.append('nonce', ppnNonce); |
| 192 |
|
| 193 |
// 1) BUILD REQUEST |
| 194 |
if (mode === 'link') { |
| 195 |
const form = el.dataset.form ? document.getElementById(el.dataset.form) : el.closest('form'); |
| 196 |
if (!form) return; |
| 197 |
for (const [key, val] of new FormData(form)) body.append(key, val); |
| 198 |
body.append('mode', 'link'); |
| 199 |
} else { |
| 200 |
const config = PAGE_TYPES[target]; |
| 201 |
if (!config) return; |
| 202 |
body.append('mode', 'create'); |
| 203 |
body.append('target', target); |
| 204 |
body.append('content', config.shortcode(el.dataset.id)); |
| 205 |
body.append('pageTitle', el.dataset.title || config.defaultTitle(el.dataset.id)); |
| 206 |
if (config.hasTargetId && el.dataset.id) body.append('targetId', el.dataset.id); |
| 207 |
} |
| 208 |
|
| 209 |
// 2) FETCH |
| 210 |
let result; |
| 211 |
try { |
| 212 |
const resp = await fetch(ajaxurl, { method: 'POST', body: body }); |
| 213 |
const data = await resp.json(); |
| 214 |
if (!data.success) throw new Error(); |
| 215 |
result = data.data; |
| 216 |
} catch (e) { |
| 217 |
alert(__('Something went wrong. Please refresh and try again.')); |
| 218 |
return; |
| 219 |
} |
| 220 |
|
| 221 |
const dialog = document.querySelector('dialog[open]'); |
| 222 |
if (dialog) dialog.close(); |
| 223 |
|
| 224 |
// 3) UPDATE UI |
| 225 |
switch (mode) { |
| 226 |
case 'link': |
| 227 |
case 'dialog': { |
| 228 |
const linkRow = document.getElementById('ppn-page-link-row'); |
| 229 |
if (!linkRow) { |
| 230 |
const tabMap = { Program: 'programs', List: 'groups', Application: 'applications' }; |
| 231 |
_reloadTab(tabMap[target || body.get('target')]); |
| 232 |
break; |
| 233 |
} |
| 234 |
|
| 235 |
const input = document.getElementById('ppn-page-link-input'); |
| 236 |
if (input) input.value = result.permalink; |
| 237 |
|
| 238 |
linkRow.querySelectorAll('.ppn-page-link-view').forEach(el => el.remove()); |
| 239 |
|
| 240 |
const viewLink = document.createElement('a'); |
| 241 |
viewLink.href = result.permalink; |
| 242 |
viewLink.target = '_blank'; |
| 243 |
viewLink.className = 'ppn-page-link-view'; |
| 244 |
viewLink.title = __('View Page'); |
| 245 |
viewLink.innerHTML = '<i class="material-icons-outlined">open_in_new</i>'; |
| 246 |
linkRow.appendChild(viewLink); |
| 247 |
|
| 248 |
const editLink = document.createElement('a'); |
| 249 |
editLink.href = result.edit_url; |
| 250 |
editLink.target = '_blank'; |
| 251 |
editLink.className = 'ppn-page-link-view'; |
| 252 |
editLink.title = __('Edit Page'); |
| 253 |
editLink.innerHTML = '<i class="material-icons-outlined">edit</i>'; |
| 254 |
linkRow.appendChild(editLink); |
| 255 |
|
| 256 |
const linkBtn = document.getElementById('ppn-link-page-btn'); |
| 257 |
if (linkBtn) linkBtn.textContent = __('Change Page'); |
| 258 |
break; |
| 259 |
} |
| 260 |
|
| 261 |
case 'list': |
| 262 |
_reloadTab({ Program: 'programs', List: 'groups', Application: 'applications' }[target]); |
| 263 |
break; |
| 264 |
|
| 265 |
case 'group': { |
| 266 |
const status = document.getElementById(`ppn-show-status-${el.dataset.id}`); |
| 267 |
if (status) { |
| 268 |
status.className = 'ppn-page-status'; |
| 269 |
status.innerHTML = `<a class="ppn-page-status--linked" target="_blank" href="${result.permalink}">${__('View Page')}</a>`; |
| 270 |
} |
| 271 |
el.remove(); |
| 272 |
break; |
| 273 |
} |
| 274 |
|
| 275 |
case 'singleton': { |
| 276 |
const link = document.createElement('a'); |
| 277 |
link.className = 'button'; |
| 278 |
link.href = result.edit_url; |
| 279 |
link.target = '_blank'; |
| 280 |
link.textContent = el.dataset.editLabel || __('Edit Page'); |
| 281 |
el.replaceWith(link); |
| 282 |
break; |
| 283 |
} |
| 284 |
} |
| 285 |
} |
| 286 |
|
| 287 |
|
| 288 |
// =============== |
| 289 |
// LIST MANAGEMENT |
| 290 |
// =============== |
| 291 |
|
| 292 |
/** toggle show in/out of group list, update sidebar preview */ |
| 293 |
function updateListOfShows(el) { |
| 294 |
var inList = el.checked; |
| 295 |
var title = el.dataset.title; |
| 296 |
var list = document.getElementById('shows-in-group'); |
| 297 |
if (!list) return; |
| 298 |
|
| 299 |
ppnMarkUnsaved(); |
| 300 |
|
| 301 |
if (inList) { |
| 302 |
var empty = list.querySelector('.ppn-manage__show-item--empty'); |
| 303 |
if (empty) empty.remove(); |
| 304 |
|
| 305 |
const programId = el.dataset.programId; |
| 306 |
var li = document.createElement('li'); |
| 307 |
li.className = 'ppn-manage__show-item'; |
| 308 |
li.dataset.title = title; |
| 309 |
li.textContent = title; |
| 310 |
|
| 311 |
if (el.dataset.hasPage === '0') { |
| 312 |
const status = document.createElement('span'); |
| 313 |
status.id = `ppn-show-status-${programId}`; |
| 314 |
status.className = 'ppn-page-status ppn-page-status--missing'; |
| 315 |
status.textContent = __('Not Linked'); |
| 316 |
li.appendChild(status); |
| 317 |
|
| 318 |
const btn = document.createElement('button'); |
| 319 |
btn.type = 'button'; |
| 320 |
btn.className = 'ppn-icon-btn'; |
| 321 |
btn.dataset.ppnAction = 'ppnPageAction'; |
| 322 |
btn.dataset.mode = 'group'; |
| 323 |
btn.dataset.target = 'Program'; |
| 324 |
btn.dataset.id = el.dataset.programId; |
| 325 |
btn.dataset.title = title; |
| 326 |
btn.innerHTML = '<i class="material-icons-outlined">note_add</i>'; |
| 327 |
li.appendChild(btn); |
| 328 |
} |
| 329 |
|
| 330 |
list.appendChild(li); |
| 331 |
} else { |
| 332 |
list.querySelectorAll('.ppn-manage__show-item').forEach(function(item) { |
| 333 |
if (item.dataset.title === title) item.remove(); |
| 334 |
}); |
| 335 |
if (!list.querySelector('.ppn-manage__show-item')) { |
| 336 |
var emptyLi = document.createElement('li'); |
| 337 |
emptyLi.className = 'ppn-manage__show-item--empty'; |
| 338 |
emptyLi.textContent = 'No shows in this group yet'; |
| 339 |
list.appendChild(emptyLi); |
| 340 |
} |
| 341 |
} |
| 342 |
} |
| 343 |
|
| 344 |
function ppnMarkUnsaved() { |
| 345 |
var indicator = document.getElementById('ppn-unsaved-indicator'); |
| 346 |
if (indicator) indicator.style.display = ''; |
| 347 |
} |
| 348 |
|
| 349 |
|
| 350 |
// =============== |
| 351 |
// SEARCH / FILTER |
| 352 |
// =============== |
| 353 |
|
| 354 |
/** filter show checkboxes by title (managelist.php) */ |
| 355 |
function filterShows(el) { |
| 356 |
var searchText = el.value.toLowerCase(); |
| 357 |
var rows = document.querySelectorAll('.show-row'); |
| 358 |
rows.forEach(function(row) { |
| 359 |
var title = (row.getAttribute('data-title') || '').toLowerCase(); |
| 360 |
row.style.display = title.includes(searchText) ? '' : 'none'; |
| 361 |
}); |
| 362 |
} |
| 363 |
|
| 364 |
/** filter list rows by data-title within target container, skip dividers */ |
| 365 |
function filterList(el) { |
| 366 |
var searchText = el.value.toLowerCase(); |
| 367 |
var container = document.getElementById(el.dataset.ppnTarget); |
| 368 |
if (!container) return; |
| 369 |
|
| 370 |
// skip empty rows + dividers |
| 371 |
var rows = container.querySelectorAll('.ppn-list__row:not(.ppn-list__divider):not(.ppn-list__empty)'); |
| 372 |
rows.forEach(function(row) { |
| 373 |
var title = (row.getAttribute('data-title') || '').toLowerCase(); |
| 374 |
row.style.display = title.includes(searchText) ? '' : 'none'; |
| 375 |
}); |
| 376 |
} |
| 377 |
|
| 378 |
|
| 379 |
// ==================== |
| 380 |
// APPLICATION APPROVAL |
| 381 |
// ==================== |
| 382 |
|
| 383 |
function _appStatusSelectHtml(applicantId, selectedValue) { |
| 384 |
var safeId = escapeHtml(String(applicantId)); |
| 385 |
var attrs = `data-ppn-action="approveProgram" data-applicant-id="${safeId}"`; |
| 386 |
var pSel = selectedValue === '0' ? ' selected' : ''; |
| 387 |
var aSel = selectedValue === '1' ? ' selected' : ''; |
| 388 |
var rSel = selectedValue === '-1' ? ' selected' : ''; |
| 389 |
return `<select ${attrs} class="application-dropdown"><option value="0"${pSel}>${__('Pending')}</option><option value="1"${aSel}>${__('Approve')}</option><option value="-1"${rSel}>${__('Reject')}</option></select>`; |
| 390 |
} |
| 391 |
|
| 392 |
function _appStatusLabelHtml(selectedValue) { |
| 393 |
var label = selectedValue === '1' ? __('Approved') : (selectedValue === '-1' ? __('Rejected') : __('Pending')); |
| 394 |
var mod = selectedValue === '1' ? 'approved' : (selectedValue === '-1' ? 'rejected' : 'pending'); |
| 395 |
return `<span class="ppn-app-status-label ppn-app-status-label--${mod}">${label}</span>`; |
| 396 |
} |
| 397 |
|
| 398 |
/** approve/reject/undo/delete applicant via ajax, sync table + modal */ |
| 399 |
async function approveProgram(el) { |
| 400 |
var applicantId = el.dataset.applicantId; |
| 401 |
var isDelete = el.dataset.delete === 'true'; |
| 402 |
|
| 403 |
// 1) DELETE VIA BUTTON |
| 404 |
if (isDelete) { |
| 405 |
if (!confirm(__('Are you sure you want to delete this application?'))) return; |
| 406 |
|
| 407 |
var body = new FormData(); |
| 408 |
body.append('appAction', 'delete'); |
| 409 |
body.append('applicantId', applicantId); |
| 410 |
body.append('changeOrCreate', 'true'); |
| 411 |
if (typeof ppnNonce !== 'undefined') body.append('_ppn_nonce', ppnNonce); |
| 412 |
|
| 413 |
if (!await _ppnPost(body)) return; |
| 414 |
|
| 415 |
// remove row + close/remove dialog |
| 416 |
var tableCell = document.getElementById(`app-status-${applicantId}`); |
| 417 |
if (tableCell) { |
| 418 |
var row = tableCell.closest('.ppn-list__row'); |
| 419 |
if (row) row.remove(); |
| 420 |
} |
| 421 |
var openDialog = document.querySelector(`dialog#ppn-app-${applicantId}`); |
| 422 |
if (openDialog && openDialog.open) openDialog.close(); |
| 423 |
var modalWrap = document.getElementById(`ppn-app-${applicantId}`); |
| 424 |
if (modalWrap) modalWrap.remove(); |
| 425 |
return; |
| 426 |
} |
| 427 |
|
| 428 |
// 2) STATUS CHANGE VIA SELECT |
| 429 |
if (el.tagName !== 'SELECT') return; |
| 430 |
var selectedValue = el.value; |
| 431 |
var action; |
| 432 |
if (selectedValue === '1') action = 'approve'; |
| 433 |
else if (selectedValue === '-1') action = 'disapprove'; |
| 434 |
else action = 'undo'; |
| 435 |
|
| 436 |
var body = new FormData(); |
| 437 |
body.append('appAction', action); |
| 438 |
body.append('applicantId', applicantId); |
| 439 |
body.append('changeOrCreate', 'true'); |
| 440 |
if (typeof ppnNonce !== 'undefined') body.append('_ppn_nonce', ppnNonce); |
| 441 |
|
| 442 |
if (!await _ppnPost(body)) return; |
| 443 |
|
| 444 |
// 3) SYNC UI: table label + modal select, reload shows tab |
| 445 |
var tableCell = document.getElementById(`app-status-${applicantId}`); |
| 446 |
var modalCell = document.getElementById(`app-modal-status-${applicantId}`); |
| 447 |
if (tableCell) tableCell.innerHTML = _appStatusLabelHtml(selectedValue); |
| 448 |
if (modalCell) modalCell.innerHTML = _appStatusSelectHtml(applicantId, selectedValue); |
| 449 |
_reloadTab('programs'); |
| 450 |
} |
| 451 |
|
| 452 |
/** refetch page html and swap tab content by id */ |
| 453 |
async function _reloadTab(tabId) { |
| 454 |
try { |
| 455 |
// 1) BUILD URL w/ tab param |
| 456 |
var tabParam = tabId === 'programs' ? 'shows' : tabId; |
| 457 |
var url = window.location.href.split('#')[0]; |
| 458 |
if (url.indexOf('tab=') === -1) { |
| 459 |
url += (url.indexOf('?') === -1 ? '?' : '&') + 'tab=' + tabParam; |
| 460 |
} |
| 461 |
|
| 462 |
// 2) FETCH + PARSE response html |
| 463 |
var resp = await fetch(url, { credentials: 'same-origin' }); |
| 464 |
if (!resp.ok) return; |
| 465 |
var doc = new DOMParser().parseFromString(await resp.text(), 'text/html'); |
| 466 |
|
| 467 |
// 3) SWAP tab content or full reload as fallback |
| 468 |
var fresh = doc.getElementById(tabId); |
| 469 |
var current = document.getElementById(tabId); |
| 470 |
if (fresh && current) { |
| 471 |
current.querySelectorAll('dialog[open]').forEach(function(d) { d.close(); }); |
| 472 |
current.innerHTML = fresh.innerHTML; |
| 473 |
} else { |
| 474 |
location.reload(); |
| 475 |
} |
| 476 |
} catch (e) { |
| 477 |
// silent fail, updates on next page load |
| 478 |
} |
| 479 |
} |
| 480 |
|
| 481 |
|
| 482 |
// ================== |
| 483 |
// PROGRAM MANAGEMENT |
| 484 |
// ================== |
| 485 |
|
| 486 |
/** open page-select dialog pre-filled w/ program shortcode */ |
| 487 |
function editPageForProgram(el) { |
| 488 |
var programId = el.dataset.programId; |
| 489 |
var programTitle = el.dataset.programTitle || ''; |
| 490 |
document.getElementById('select-page-target-id').value = programId; |
| 491 |
document.getElementById('page-select-ppn').value = el.dataset.linkPage; |
| 492 |
document.getElementById('ppn-program-shortcode').value = `[ppn-program id="${programId}"]`; |
| 493 |
// populate create-new-page btn w/ program context |
| 494 |
var createBtn = document.getElementById('ppn-create-page-btn'); |
| 495 |
if (createBtn) { |
| 496 |
createBtn.dataset.id = programId; |
| 497 |
createBtn.dataset.title = programTitle; |
| 498 |
} |
| 499 |
|
| 500 |
const editLink = document.getElementById('ppn-edit-page-link'); |
| 501 |
if (editLink) { |
| 502 |
if (el.dataset.linkPage) { |
| 503 |
editLink.href = `post.php?post=${el.dataset.linkPage}&action=edit`; |
| 504 |
editLink.style.display = ''; |
| 505 |
} else { |
| 506 |
editLink.style.display = 'none'; |
| 507 |
} |
| 508 |
} |
| 509 |
|
| 510 |
var dialog = document.getElementById('selectPageBox'); |
| 511 |
if (dialog) dialog.showModal(); |
| 512 |
} |
| 513 |
|
| 514 |
function addToGroup(el) { |
| 515 |
var programId = el.dataset.programId; |
| 516 |
var input = document.getElementById('add-program-to-group'); |
| 517 |
if (input) input.value = programId; |
| 518 |
var dialog = document.getElementById('addToGroup'); |
| 519 |
if (dialog) dialog.showModal(); |
| 520 |
} |
| 521 |
|
| 522 |
|
| 523 |
// ======== |
| 524 |
// SETTINGS |
| 525 |
// ======== |
| 526 |
|
| 527 |
/** save tos url via fetch, show saved/error feedback */ |
| 528 |
function saveTosUrl(el) { |
| 529 |
const form = document.getElementById(el.dataset.formId); |
| 530 |
if (!form) return; |
| 531 |
const btn = form.querySelector('button[data-ppn-action="saveTosUrl"]'); |
| 532 |
if (!btn) return; |
| 533 |
const originalText = btn.textContent; |
| 534 |
|
| 535 |
btn.textContent = 'Saving...'; |
| 536 |
btn.disabled = true; |
| 537 |
|
| 538 |
fetch(window.location.href, { |
| 539 |
method: 'POST', |
| 540 |
body: new FormData(form) |
| 541 |
}).then(function(response) { |
| 542 |
if (response.ok) { |
| 543 |
btn.textContent = 'Saved!'; |
| 544 |
|
| 545 |
// clear error message if existing |
| 546 |
const existing = document.getElementById('ppn-tos-error'); |
| 547 |
if (existing) existing.remove(); |
| 548 |
|
| 549 |
setTimeout(function() { |
| 550 |
btn.textContent = originalText; |
| 551 |
btn.disabled = false; |
| 552 |
}, 2000); |
| 553 |
} else { |
| 554 |
btn.textContent = 'Error'; |
| 555 |
btn.disabled = false; |
| 556 |
const existing = document.getElementById('ppn-tos-error'); |
| 557 |
if (existing) existing.remove(); |
| 558 |
const errDiv = document.createElement('div'); |
| 559 |
errDiv.id = 'ppn-tos-error'; |
| 560 |
errDiv.className = 'error powerpress-error inline'; |
| 561 |
errDiv.textContent = 'Please enter a valid URL for your Terms of Service'; |
| 562 |
form.parentNode.insertBefore(errDiv, form); |
| 563 |
|
| 564 |
setTimeout(function() { |
| 565 |
btn.textContent = originalText; |
| 566 |
btn.disabled = false; |
| 567 |
}, 2000); |
| 568 |
} |
| 569 |
}).catch(function() { |
| 570 |
btn.textContent = 'Error'; |
| 571 |
btn.disabled = false; |
| 572 |
}); |
| 573 |
} |
| 574 |
|
| 575 |
|
| 576 |
// ==== |
| 577 |
// INIT |
| 578 |
// ==== |
| 579 |
|
| 580 |
// strip irrelevant fields from submit application form |
| 581 |
function ppnInitSubmitApp() { |
| 582 |
var feedUrl = document.getElementById('feedUrl'); |
| 583 |
if (!feedUrl) return; |
| 584 |
if (!feedUrl.hasAttribute('readonly')) { |
| 585 |
var addInfo = document.getElementById('addInfo'); |
| 586 |
if (addInfo) addInfo.remove(); |
| 587 |
} else { |
| 588 |
var find = document.getElementById('findProgram'); |
| 589 |
var back = document.getElementById('backProgram'); |
| 590 |
if (find) find.remove(); |
| 591 |
if (back) back.remove(); |
| 592 |
} |
| 593 |
} |
| 594 |
|
| 595 |
async function addProgramToNetwork(el) { |
| 596 |
const icon = document.getElementById(el.id); |
| 597 |
|
| 598 |
// ignore clicks on shows that have already been added |
| 599 |
if(!icon.src.includes('circlecheck_blue')){ |
| 600 |
const body = new FormData(); |
| 601 |
body.append('action', 'add_program_to_network'); |
| 602 |
body.append('nonce', ppnNonce); |
| 603 |
body.append('program_id', el.dataset.programId); |
| 604 |
body.append('network_id', el.dataset.networkId); |
| 605 |
body.append('program_title', el.dataset.title || ''); |
| 606 |
|
| 607 |
const resp = await fetch(ajaxurl, { method: 'POST', body: body }); |
| 608 |
const data = await resp.json(); |
| 609 |
|
| 610 |
if(data.success === true){ |
| 611 |
icon.src = el.dataset.updatedSrc; |
| 612 |
if(data.data && data.data.message) showToast(data.data.message, { id: 'ppn-add-toast' }); |
| 613 |
} else { |
| 614 |
// do we need any error messaging? |
| 615 |
} |
| 616 |
} |
| 617 |
} |
| 618 |
|
| 619 |
|
| 620 |
// ================ |
| 621 |
// EVENT DELEGATION |
| 622 |
// ================ |
| 623 |
|
| 624 |
document.addEventListener('DOMContentLoaded', function() { |
| 625 |
// data-ppn-action -> handler map |
| 626 |
var handlers = { |
| 627 |
ppnAction: _ppnAction, |
| 628 |
ppnPageAction: _ppnPageAction, |
| 629 |
showPPNTab: function(el) { showPPNTab(el.dataset.tab); }, |
| 630 |
approveProgram: approveProgram, |
| 631 |
editPageForProgram: editPageForProgram, |
| 632 |
addToGroup: addToGroup, |
| 633 |
ppCopyText: function(el) { ppCopyText(el.dataset.inputId, el); }, |
| 634 |
ppnDialog: ppnDialog, |
| 635 |
ppnDialogClose: ppnDialogClose, |
| 636 |
saveTosUrl: saveTosUrl, |
| 637 |
updateListOfShows: updateListOfShows, |
| 638 |
filterShows: filterShows, |
| 639 |
filterList: filterList, |
| 640 |
addProgramToNetwork: addProgramToNetwork |
| 641 |
}; |
| 642 |
|
| 643 |
function dispatch(action, el) { |
| 644 |
if (handlers[action]) handlers[action](el); |
| 645 |
} |
| 646 |
|
| 647 |
// CLICK DELEGATION (backdrop close, toggle, action dispatch) |
| 648 |
document.addEventListener('click', function(e) { |
| 649 |
// backdrop click closes open dialogs |
| 650 |
if (e.target.tagName === 'DIALOG' && e.target.open) { |
| 651 |
e.target.close(); |
| 652 |
return; |
| 653 |
} |
| 654 |
|
| 655 |
// collapse/expand toggle |
| 656 |
var toggle = e.target.closest('.ppn-toggle'); |
| 657 |
if (toggle) { |
| 658 |
var collapsed = toggle.classList.toggle('ppn-toggle--collapsed'); |
| 659 |
var chevron = toggle.querySelector('.ppn-toggle__chevron'); |
| 660 |
if (chevron) chevron.textContent = collapsed ? 'expand_more' : 'expand_less'; |
| 661 |
return; |
| 662 |
} |
| 663 |
|
| 664 |
// action dispatch |
| 665 |
var el = e.target.closest('[data-ppn-action]'); |
| 666 |
if (!el) return; |
| 667 |
var action = el.dataset.ppnAction; |
| 668 |
// skip change/keyup only handlers |
| 669 |
if (action === 'updateListOfShows' || action === 'filterShows' || action === 'filterList') return; |
| 670 |
// selects handle via change event |
| 671 |
if (el.tagName === 'SELECT') return; |
| 672 |
e.preventDefault(); |
| 673 |
dispatch(action, el); |
| 674 |
}); |
| 675 |
|
| 676 |
// CHANGE DELEGATION |
| 677 |
document.addEventListener('change', function(e) { |
| 678 |
var el = e.target.closest('[data-ppn-action]'); |
| 679 |
if (!el) return; |
| 680 |
dispatch(el.dataset.ppnAction, el); |
| 681 |
}); |
| 682 |
|
| 683 |
// KEYUP DELEGATION |
| 684 |
document.addEventListener('keyup', function(e) { |
| 685 |
var el = e.target.closest('[data-ppn-action]'); |
| 686 |
if (!el) return; |
| 687 |
dispatch(el.dataset.ppnAction, el); |
| 688 |
}); |
| 689 |
|
| 690 |
// NETWORK SHOW SEARCH ENTER KEY |
| 691 |
var searchInput = document.getElementById('network-show-search-input'); |
| 692 |
if (searchInput) { |
| 693 |
searchInput.addEventListener('keydown', function(e) { |
| 694 |
if (e.key === 'Enter') { |
| 695 |
e.preventDefault(); |
| 696 |
if (typeof submitNetworkShowSearch === 'function') { |
| 697 |
submitNetworkShowSearch(); |
| 698 |
} |
| 699 |
} |
| 700 |
}); |
| 701 |
} |
| 702 |
|
| 703 |
// TOS URL ENTER KEY |
| 704 |
const tosForm = document.getElementById('tosUrlForm'); |
| 705 |
if (tosForm) { |
| 706 |
tosForm.addEventListener('submit', function(e) { |
| 707 |
e.preventDefault(); |
| 708 |
const btn = tosForm.querySelector('[data-ppn-action="saveTosUrl"]'); |
| 709 |
if (btn) btn.click(); |
| 710 |
}); |
| 711 |
} |
| 712 |
|
| 713 |
// AUTO-INIT |
| 714 |
ppnInitSubmitApp(); |
| 715 |
|
| 716 |
document.querySelectorAll('[data-char-counter]').forEach(function(input) { |
| 717 |
var warnAt = input.dataset.charWarn ? parseInt(input.dataset.charWarn, 10) : null; |
| 718 |
initCharCounter(input.id, input.dataset.charCounter, warnAt); |
| 719 |
}); |
| 720 |
|
| 721 |
var editForm = document.getElementById('editForm') || document.getElementById('createForm'); |
| 722 |
if (editForm) { |
| 723 |
editForm.addEventListener('input', ppnMarkUnsaved); |
| 724 |
} |
| 725 |
}); |
| 726 |
|