Initial public release of BreznFlow, an n8n workflow renderer for WordPress. Fully PHPCS-compliant (WordPress Coding Standards), security-hardened, and ready for WordPress.org plugin review. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
199 lines
7.6 KiB
JavaScript
199 lines
7.6 KiB
JavaScript
/* BreznFlow — Admin JS (vanilla ES2020, no dependencies) */
|
|
/* global breznflowAdmin */
|
|
|
|
(function () {
|
|
'use strict';
|
|
|
|
// ── AJAX helpers ──────────────────────────────────────────────────────────
|
|
|
|
function post(action, data, callback) {
|
|
const params = new URLSearchParams();
|
|
params.append('action', action);
|
|
params.append('nonce', breznflowAdmin.nonce);
|
|
for (const [k, v] of Object.entries(data)) {
|
|
params.append(k, v);
|
|
}
|
|
|
|
fetch(breznflowAdmin.ajaxUrl, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
body: params.toString(),
|
|
})
|
|
.then(function(r) { return r.json(); })
|
|
.then(callback)
|
|
.catch(function(err) {
|
|
callback({ success: false, data: { message: String(err) } });
|
|
});
|
|
}
|
|
|
|
// ── Step 1: Validation ────────────────────────────────────────────────────
|
|
|
|
const jsonTextarea = document.getElementById('breznflow-json');
|
|
const validateBtn = document.getElementById('breznflow-validate-btn');
|
|
const submitBtn = document.getElementById('breznflow-step1-submit');
|
|
const resultDiv = document.getElementById('breznflow-validation-result');
|
|
|
|
function showResult(success, message) {
|
|
if (!resultDiv) return;
|
|
resultDiv.removeAttribute('hidden');
|
|
resultDiv.className = success ? 'success' : 'error';
|
|
resultDiv.textContent = message;
|
|
}
|
|
|
|
if (validateBtn && jsonTextarea) {
|
|
validateBtn.addEventListener('click', function () {
|
|
const json = jsonTextarea.value.trim();
|
|
if (!json) {
|
|
showResult(false, breznflowAdmin.i18n.pasteFirst || 'Please paste a workflow JSON first.');
|
|
return;
|
|
}
|
|
validateBtn.disabled = true;
|
|
validateBtn.textContent = breznflowAdmin.i18n.validating || 'Validating...';
|
|
|
|
post('breznflow_validate_json', { json: json }, function(resp) {
|
|
validateBtn.disabled = false;
|
|
validateBtn.textContent = breznflowAdmin.i18n.validateJson || 'Validate JSON';
|
|
|
|
if (resp.success) {
|
|
const msg = (breznflowAdmin.i18n.valid || 'Valid n8n workflow') +
|
|
': ' + resp.data.name + ' \u2014 ' + resp.data.nodes + ' ' + (breznflowAdmin.i18n.nodes || 'nodes');
|
|
showResult(true, msg);
|
|
if (submitBtn) submitBtn.disabled = false;
|
|
} else {
|
|
showResult(false, (breznflowAdmin.i18n.invalid || 'Invalid') + ': ' + (resp.data && resp.data.message ? resp.data.message : 'Unknown error'));
|
|
if (submitBtn) submitBtn.disabled = true;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// ── Step 1: File upload → textarea ────────────────────────────────────────
|
|
|
|
const fileInput = document.getElementById('breznflow-file');
|
|
if (fileInput && jsonTextarea) {
|
|
fileInput.addEventListener('change', function () {
|
|
const file = this.files && this.files[0];
|
|
if (!file) return;
|
|
const reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
jsonTextarea.value = e.target.result;
|
|
if (resultDiv) {
|
|
resultDiv.setAttribute('hidden', '');
|
|
resultDiv.className = '';
|
|
resultDiv.textContent = '';
|
|
}
|
|
if (submitBtn) submitBtn.disabled = true;
|
|
};
|
|
reader.readAsText(file);
|
|
});
|
|
}
|
|
|
|
// ── Step 1: URL fetch ─────────────────────────────────────────────────────
|
|
|
|
const urlInput = document.getElementById('breznflow-url');
|
|
const fetchBtn = document.getElementById('breznflow-fetch-url');
|
|
if (fetchBtn && urlInput && jsonTextarea) {
|
|
fetchBtn.addEventListener('click', function () {
|
|
const url = urlInput.value.trim();
|
|
if (!url) return;
|
|
fetchBtn.disabled = true;
|
|
fetchBtn.textContent = breznflowAdmin.i18n.fetching || 'Fetching...';
|
|
|
|
post('breznflow_fetch_url', { url: url }, function(resp) {
|
|
fetchBtn.disabled = false;
|
|
fetchBtn.textContent = breznflowAdmin.i18n.fetch || 'Fetch';
|
|
if (resp.success && resp.data && resp.data.json) {
|
|
jsonTextarea.value = resp.data.json;
|
|
if (submitBtn) submitBtn.disabled = true;
|
|
if (resultDiv) {
|
|
resultDiv.setAttribute('hidden', '');
|
|
resultDiv.textContent = '';
|
|
}
|
|
} else {
|
|
showResult(false, resp.data && resp.data.message ? resp.data.message : (breznflowAdmin.i18n.fetchFailed || 'Fetch failed'));
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// ── Copy Shortcode buttons ────────────────────────────────────────────────
|
|
|
|
function attachCopyButtons() {
|
|
document.querySelectorAll('.breznflow-copy-sc').forEach(function(btn) {
|
|
btn.addEventListener('click', function () {
|
|
const sc = btn.getAttribute('data-sc') || '';
|
|
const label = btn.textContent;
|
|
if (!sc) return;
|
|
|
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
navigator.clipboard.writeText(sc).then(function() {
|
|
btn.textContent = breznflowAdmin.i18n.copied || 'Copied!';
|
|
setTimeout(function() { btn.textContent = label; }, 2000);
|
|
}).catch(function() {
|
|
fallbackCopy(sc, btn, label);
|
|
});
|
|
} else {
|
|
fallbackCopy(sc, btn, label);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function fallbackCopy(text, btn, label) {
|
|
const ta = document.createElement('textarea');
|
|
ta.value = text;
|
|
ta.style.position = 'fixed';
|
|
ta.style.opacity = '0';
|
|
document.body.appendChild(ta);
|
|
ta.focus();
|
|
ta.select();
|
|
try {
|
|
document.execCommand('copy');
|
|
btn.textContent = breznflowAdmin.i18n.copied || 'Copied!';
|
|
setTimeout(function() { btn.textContent = label; }, 2000);
|
|
} catch (e) {
|
|
// silently ignore
|
|
}
|
|
document.body.removeChild(ta);
|
|
}
|
|
|
|
attachCopyButtons();
|
|
|
|
// ── Step 2: Live shortcode preview ────────────────────────────────────────
|
|
|
|
const scLive = document.getElementById('breznflow-shortcode-live');
|
|
if (scLive) {
|
|
const postIdInput = document.querySelector('input[name="breznflow_post_id"]');
|
|
const postId = postIdInput ? postIdInput.value : '';
|
|
const modeRadios = document.querySelectorAll('input[name="default_mode"]');
|
|
const showTitleCb = document.querySelector('input[name="show_title"]');
|
|
const showInfoCb = document.querySelector('input[name="show_infobox"]');
|
|
const showDlCb = document.querySelector('input[name="show_download"]');
|
|
const zoomInput = document.querySelector('input[name="default_zoom"]');
|
|
|
|
function updatePreview() {
|
|
let sc = '[breznflow id="' + postId + '"';
|
|
const modeVal = document.querySelector('input[name="default_mode"]:checked');
|
|
if (modeVal && modeVal.value !== 'visual') sc += ' mode="' + modeVal.value + '"';
|
|
if (zoomInput && zoomInput.value !== '100') sc += ' zoom="' + zoomInput.value + '"';
|
|
if (showTitleCb && !showTitleCb.checked) sc += ' show_title="0"';
|
|
if (showInfoCb && !showInfoCb.checked) sc += ' show_infobox="0"';
|
|
if (showDlCb && showDlCb.checked) sc += ' show_download="1"';
|
|
sc += ']';
|
|
scLive.textContent = sc;
|
|
|
|
// Update copy button data attribute
|
|
const copyBtn = scLive.nextElementSibling;
|
|
if (copyBtn && copyBtn.classList.contains('breznflow-copy-sc')) {
|
|
copyBtn.setAttribute('data-sc', sc);
|
|
}
|
|
}
|
|
|
|
modeRadios.forEach(function(r) { r.addEventListener('change', updatePreview); });
|
|
if (showTitleCb) showTitleCb.addEventListener('change', updatePreview);
|
|
if (showInfoCb) showInfoCb.addEventListener('change', updatePreview);
|
|
if (showDlCb) showDlCb.addEventListener('change', updatePreview);
|
|
if (zoomInput) zoomInput.addEventListener('input', updatePreview);
|
|
}
|
|
|
|
}());
|