Files
backPRO/assets/js/app.js
Jacek Pyziak 4d5e220b3c Add BackPRO News theme and update database schema for article tracking
- Introduced a new WordPress theme "BackPRO News" with a lightweight magazine-style design.
- Added columns for tracking retry attempts and timestamps for unpublished/generated articles in the articles table.
- Included remote service metadata fields in the sites table for better management.
- Created log files for image replacements, installer actions, OpenAI article generation, and publishing processes.
- Implemented a dashboard template for site management, including permalink settings and theme installation options.
2026-02-17 20:08:02 +01:00

295 lines
12 KiB
JavaScript

// BackPRO - Frontend Scripts
(function () {
'use strict';
var confirmQueue = Promise.resolve();
var confirmUi = null;
function getToastClass(type) {
if (type === 'success') return 'text-bg-success';
if (type === 'danger' || type === 'error') return 'text-bg-danger';
if (type === 'warning') return 'text-bg-warning';
return 'text-bg-primary';
}
function getToastTitle(type) {
if (type === 'success') return 'Sukces';
if (type === 'danger' || type === 'error') return 'Blad';
if (type === 'warning') return 'Uwaga';
return 'Informacja';
}
function ensureToastContainer() {
var container = document.getElementById('bpToastContainer');
if (container) return container;
container = document.createElement('div');
container.id = 'bpToastContainer';
container.className = 'toast-container position-fixed top-0 end-0 p-3 bp-toast-container';
document.body.appendChild(container);
return container;
}
function showToast(message, type, options) {
if (!message) return;
if (!window.bootstrap || !window.bootstrap.Toast) {
var fallbackContainer = ensureToastContainer();
var fallbackToast = document.createElement('div');
fallbackToast.className = 'bp-toast-fallback';
fallbackToast.textContent = message;
fallbackContainer.appendChild(fallbackToast);
setTimeout(function () {
fallbackToast.remove();
}, 4000);
return;
}
var opts = options || {};
var container = ensureToastContainer();
var toast = document.createElement('div');
var toastClass = getToastClass(type || 'info');
var title = opts.title || getToastTitle(type || 'info');
var delay = typeof opts.delay === 'number' ? opts.delay : 5000;
toast.className = 'toast border-0 shadow-sm bp-toast ' + toastClass;
toast.setAttribute('role', 'alert');
toast.setAttribute('aria-live', 'assertive');
toast.setAttribute('aria-atomic', 'true');
toast.innerHTML =
'<div class="d-flex">' +
'<div class="toast-body">' +
'<strong class="me-1">' + title + ':</strong> ' + message +
'</div>' +
'<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Zamknij"></button>' +
'</div>';
container.appendChild(toast);
var bsToast = new bootstrap.Toast(toast, { delay: delay });
toast.addEventListener('hidden.bs.toast', function () {
toast.remove();
});
bsToast.show();
}
function ensureConfirmUi() {
if (confirmUi) return confirmUi;
var existing = document.getElementById('bpConfirmModal');
if (!existing) {
var modal = document.createElement('div');
modal.className = 'modal fade bp-confirm-modal';
modal.id = 'bpConfirmModal';
modal.tabIndex = -1;
modal.setAttribute('aria-hidden', 'true');
modal.innerHTML =
'<div class="modal-dialog modal-dialog-centered">' +
'<div class="modal-content">' +
'<div class="modal-header border-0 pb-1">' +
'<h5 class="modal-title" id="bpConfirmTitle">Potwierdzenie</h5>' +
'<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Zamknij"></button>' +
'</div>' +
'<div class="modal-body pt-2">' +
'<p class="mb-0" id="bpConfirmMessage"></p>' +
'</div>' +
'<div class="modal-footer border-0 pt-2">' +
'<button type="button" class="btn btn-outline-secondary" data-role="cancel">Anuluj</button>' +
'<button type="button" class="btn btn-danger" data-role="confirm">Potwierdz</button>' +
'</div>' +
'</div>' +
'</div>';
document.body.appendChild(modal);
existing = modal;
}
confirmUi = {
el: existing,
modal: window.bootstrap && window.bootstrap.Modal
? new bootstrap.Modal(existing, { backdrop: 'static', keyboard: false })
: null,
titleEl: existing.querySelector('#bpConfirmTitle'),
messageEl: existing.querySelector('#bpConfirmMessage'),
cancelBtn: existing.querySelector('[data-role="cancel"]'),
confirmBtn: existing.querySelector('[data-role="confirm"]')
};
return confirmUi;
}
function showConfirmDialog(message, options) {
var opts = options || {};
if (!window.bootstrap || !window.bootstrap.Modal) {
showToast('Brak komponentu potwierdzenia. Operacja zostala wstrzymana.', 'warning');
return Promise.resolve(false);
}
var ui = ensureConfirmUi();
ui.titleEl.textContent = opts.title || 'Potwierdzenie';
ui.messageEl.textContent = message || 'Czy na pewno?';
ui.cancelBtn.textContent = opts.cancelText || 'Anuluj';
ui.confirmBtn.textContent = opts.confirmText || 'Potwierdz';
ui.confirmBtn.className = 'btn ' + (opts.confirmClass || 'btn-danger');
return new Promise(function (resolve) {
var confirmed = false;
function cleanup() {
ui.confirmBtn.removeEventListener('click', onConfirm);
ui.cancelBtn.removeEventListener('click', onCancel);
ui.el.removeEventListener('hidden.bs.modal', onHidden);
}
function onConfirm() {
confirmed = true;
ui.modal.hide();
}
function onCancel() {
ui.modal.hide();
}
function onHidden() {
cleanup();
resolve(confirmed);
}
ui.confirmBtn.addEventListener('click', onConfirm);
ui.cancelBtn.addEventListener('click', onCancel);
ui.el.addEventListener('hidden.bs.modal', onHidden);
ui.modal.show();
});
}
function queueConfirm(message, options) {
var run = function () {
return showConfirmDialog(message, options);
};
var pending = confirmQueue.then(run, run);
confirmQueue = pending.catch(function () { return false; });
return pending;
}
function installConfirmForForms() {
document.addEventListener('submit', function (event) {
var form = event.target;
if (!(form instanceof HTMLFormElement)) return;
if (!form.matches('form[data-confirm]')) return;
if (form.dataset.confirmBypass === '1') {
form.dataset.confirmBypass = '';
return;
}
event.preventDefault();
queueConfirm(form.getAttribute('data-confirm'), {
title: form.dataset.confirmTitle || 'Potwierdzenie',
confirmText: form.dataset.confirmOk || 'Potwierdz',
cancelText: form.dataset.confirmCancel || 'Anuluj',
confirmClass: form.dataset.confirmClass || 'btn-danger'
}).then(function (ok) {
if (!ok) return;
form.dataset.confirmBypass = '1';
form.submit();
});
}, true);
}
function initTestConnectionButtons() {
document.querySelectorAll('.btn-test-connection').forEach(function (btn) {
btn.addEventListener('click', function () {
var siteId = this.dataset.siteId;
var button = this;
var originalHtml = button.innerHTML;
button.innerHTML = '<span class="spinner-border spinner-border-sm"></span>';
button.disabled = true;
fetch('/sites/' + siteId + '/test', { method: 'POST' })
.then(function (r) { return r.json(); })
.then(function (data) {
if (data.success) {
button.innerHTML = '<i class="bi bi-check-lg"></i>';
button.classList.remove('btn-outline-success');
button.classList.add('btn-success');
showToast(data.message || 'Polaczenie poprawne.', 'success', { delay: 3500 });
} else {
button.innerHTML = '<i class="bi bi-x-lg"></i>';
button.classList.remove('btn-outline-success');
button.classList.add('btn-danger');
showToast('Blad polaczenia: ' + (data.message || 'Nieznany blad'), 'danger');
}
})
.catch(function () {
button.innerHTML = '<i class="bi bi-x-lg"></i>';
button.classList.add('btn-danger');
showToast('Blad sieci podczas testu polaczenia.', 'danger');
})
.finally(function () {
button.disabled = false;
setTimeout(function () {
button.innerHTML = originalHtml;
button.className = button.className.replace('btn-success', 'btn-outline-success').replace('btn-danger', 'btn-outline-success');
}, 3000);
});
});
});
}
function initTopicEditButtons() {
document.querySelectorAll('.btn-edit-topic').forEach(function (btn) {
btn.addEventListener('click', function () {
var id = this.dataset.id;
var form = document.getElementById('topicForm');
var title = document.getElementById('topicFormTitle');
var submit = document.getElementById('topicFormSubmit');
if (!form || !title || !submit) return;
form.action = '/topics/' + id + '/update';
title.textContent = 'Edytuj temat';
submit.textContent = 'Zapisz zmiany';
document.getElementById('topic_name').value = this.dataset.name;
document.getElementById('topic_description').value = this.dataset.description;
document.getElementById('topic_wp_category').value = this.dataset.wpCategory || '';
document.getElementById('topic_is_active').checked = this.dataset.active === '1';
var globalSelect = document.getElementById('topic_global_id');
if (globalSelect) {
globalSelect.value = this.dataset.globalTopic || '';
}
});
});
}
function highlightActiveSidebarLink() {
var currentPath = window.location.pathname;
document.querySelectorAll('.sidebar .nav-link').forEach(function (link) {
var href = link.getAttribute('href');
if (currentPath === href || (href !== '/' && currentPath.startsWith(href))) {
link.classList.add('active');
}
});
}
window.BackProUI = {
toast: showToast,
confirm: queueConfirm
};
window.backproNotify = function (message, type, options) {
showToast(message, type || 'info', options);
};
window.backproConfirm = function (message, options) {
return queueConfirm(message, options);
};
document.addEventListener('DOMContentLoaded', function () {
installConfirmForForms();
initTestConnectionButtons();
initTopicEditButtons();
highlightActiveSidebarLink();
});
})();