AlkantarClanX12
| Current Path : /www/capitalgmcbuickregina_830/public/wp-content/plugins/leadbox/lib/ |
| Current File : /www/capitalgmcbuickregina_830/public/wp-content/plugins/leadbox/lib/edit-json-plugin.js |
/* eslint-disable */
/* Genius config editor — Monaco + auto-detected component sidebar.
* Falls back to a plain <textarea> if Monaco cannot be loaded, so the
* page is always usable. */
jQuery(document).ready(function ($) {
var cfg = window.editJsonPlugin || {};
var vsPath = cfg.monaco_vs || "";
var baseUrl = vsPath.replace(/\/vs$/, "") + "/";
var state = {
mode: "monaco", // or "fallback"
editor: null,
_fallbackArea: null,
components: [],
schema: null,
filter: "all",
search: "",
};
var $ta = $("#json-content");
var $list = $("#componentList");
var $status = $("#genius-status");
/* ---------- value abstraction (works for Monaco and fallback) ---------- */
function getValue() {
if (state.mode === "monaco" && state.editor) return state.editor.getValue();
if (state._fallbackArea) return state._fallbackArea.val();
return $ta.val();
}
function setValue(v) {
if (state.mode === "monaco" && state.editor) {
state.editor.setValue(v);
return;
}
if (state._fallbackArea) {
state._fallbackArea.val(v);
return;
}
$ta.val(v);
}
/* ---------- feedback ---------- */
function showToast(message, type) {
if (typeof Toastify !== "function") {
if (type === "error") console.error(message);
else console.log(message);
return;
}
Toastify({
text: message,
duration: 6000,
newWindow: true,
close: true,
gravity: "bottom",
position: "right",
stopOnFocus: true,
style: {
background: type === "success" ? "green" : type === "error" ? "red" : "#2271b1",
fontSize: "16px",
borderRadius: "5px",
},
}).showToast();
}
function setStatus(text, kind) {
$status.removeClass("is-error is-ok");
if (kind) $status.addClass(kind === "error" ? "is-error" : "is-ok");
$status.text(text || "");
}
function esc(s) {
return String(s).replace(/[&<>"']/g, function (ch) {
return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[ch];
});
}
/* ---------- modal (kept from the original) ---------- */
var modal = $("#myModal");
$(".close").on("click", function () {
modal.hide();
});
$("#close-modal-button").on("click", function () {
modal.hide();
});
$(window).on("click", function (event) {
if ($(event.target).is(modal)) modal.hide();
});
function openModal(message) {
$("#modal-body").text(message);
modal.show();
}
/* ---------- save ---------- */
function saveJson() {
var jsonContent = getValue();
try {
JSON.parse(jsonContent);
} catch (e) {
showToast("Invalid JSON format. Please fix the JSON syntax.", "error");
setStatus("Invalid JSON — not saved", "error");
return;
}
$("#saving-backdrop").show();
$.ajax({
url: cfg.ajax_url,
method: "POST",
data: {
action: "save_json_file",
json_content: jsonContent,
page: "leadbox-settings-genius",
nonce: cfg.nonce,
},
success: function (response) {
$("#saving-backdrop").hide();
if (response && response.success) {
saveOption(jsonContent);
showToast(response.data || "Saved", "success");
setStatus("Saved", "ok");
} else {
showToast((response && response.data) || "Error saving file", "error");
setStatus("Save failed", "error");
}
},
error: function (xhr, status, error) {
$("#saving-backdrop").hide();
showToast(error || "Network error", "error");
setStatus("Save failed", "error");
},
});
}
function saveOption(json) {
if (!json) return;
$.ajax({
url: cfg.ajax_url,
method: "POST",
data: {
action: "save_json_option",
json_content: json,
page: "leadbox-settings-genius",
nonce: cfg.nonce,
},
});
}
/* ---------- format ---------- */
function formatJson() {
if (state.mode === "monaco" && state.editor) {
var action = state.editor.getAction("editor.action.formatDocument");
if (action) {
action.run();
return;
}
}
var content = getValue();
try {
setValue(JSON.stringify(JSON.parse(content), null, 4));
} catch (e) {
openModal("Invalid JSON format. Please fix the JSON syntax.");
}
}
/* ---------- jump to a top-level key in the editor ---------- */
function jumpToKey(key) {
if (state.mode !== "monaco" || !state.editor || !window.monaco) return;
var model = state.editor.getModel();
if (!model) return;
var matches = model.findMatches('"' + key + '"', true, false, true, null, false);
if (!matches || !matches.length) return;
// Prefer the least-indented match that begins a key (the top-level one).
var target = null;
var bestIndent = Infinity;
for (var i = 0; i < matches.length; i++) {
var line = model.getLineContent(matches[i].range.startLineNumber);
var trimmed = line.replace(/^\s+/, "");
if (trimmed.indexOf('"' + key + '"') !== 0) continue;
var indent = line.length - trimmed.length;
if (indent < bestIndent) {
bestIndent = indent;
target = matches[i];
}
}
if (!target) target = matches[0];
state.editor.revealLineInCenter(target.range.startLineNumber);
state.editor.setSelection(target.range);
state.editor.focus();
}
/* ---------- patch a component version directly into the JSON ---------- */
function patchVersion(key, version) {
var obj;
try {
obj = JSON.parse(getValue());
} catch (e) {
showToast("Fix JSON syntax before changing versions.", "error");
return;
}
var num = parseInt(version, 10);
if (isNaN(num)) return;
if (!obj[key] || typeof obj[key] !== "object" || Array.isArray(obj[key])) {
obj[key] = { version: num, data: {} };
} else {
obj[key].version = num;
}
setValue(JSON.stringify(obj, null, 4));
for (var i = 0; i < state.components.length; i++) {
var c = state.components[i];
if (c.key === key) {
c.current = num;
c.missingInJson = false;
break;
}
}
updateCounts();
renderList();
jumpToKey(key);
showToast(key + " set to v" + num + " — remember to Save", "info");
}
/* ---------- sidebar render ---------- */
function matchesFilter(c) {
if (state.filter === "custom") return !!c.custom;
if (state.filter === "missing") return !!c.missingInJson;
return true;
}
function matchesSearch(c) {
if (!state.search) return true;
return (
c.label.toLowerCase().indexOf(state.search) !== -1 ||
c.key.toLowerCase().indexOf(state.search) !== -1
);
}
function updateCounts() {
var all = state.components.length;
var custom = 0;
var missing = 0;
state.components.forEach(function (c) {
if (c.custom) custom++;
if (c.missingInJson) missing++;
});
$('.genius-count[data-count="all"]').text(all);
$('.genius-count[data-count="custom"]').text(custom);
$('.genius-count[data-count="missing"]').text(missing);
}
function renderList() {
var visible = state.components.filter(function (c) {
return matchesFilter(c) && matchesSearch(c);
});
if (!visible.length) {
$list.html('<p class="genius-empty">No components match.</p>');
return;
}
var html = visible
.map(function (c) {
var badges = "";
if (c.custom) badges += '<span class="genius-badge custom">Custom</span>';
if (c.missingInJson) badges += '<span class="genius-badge missing">Not in JSON</span>';
var count = c.available.length;
var curTxt = c.missingInJson ? "Not set" : "Using v" + c.current;
var countTxt = count + (count === 1 ? " version" : " versions");
var opts = c.available
.map(function (v) {
var sel = !c.missingInJson && v === c.current ? " selected" : "";
return '<option value="' + v + '"' + sel + ">v" + v + "</option>";
})
.join("");
if (c.missingInJson) {
opts = '<option value="" selected>—</option>' + opts;
}
return (
'<div class="genius-card" data-key="' + esc(c.key) + '">' +
'<div class="genius-card-main">' +
'<div class="genius-card-name" title="' + esc(c.key) + '">' + esc(c.label) + "</div>" +
'<div class="genius-card-ver">' +
'<span class="using">' + curTxt + "</span>" +
'<span class="dot">·</span>' +
'<span class="count">' + countTxt + "</span>" +
"</div>" +
"</div>" +
'<div class="genius-card-right">' +
'<div class="genius-badges">' + badges + "</div>" +
'<select class="genius-version-select" data-key="' + esc(c.key) + '">' + opts + "</select>" +
"</div>" +
"</div>"
);
})
.join("");
$list.html(html);
}
/* delegated sidebar events */
$list.on("click", ".genius-card", function (event) {
if ($(event.target).closest(".genius-version-select").length) return;
jumpToKey($(this).data("key"));
});
$list.on("click", ".genius-version-select", function (event) {
event.stopPropagation();
});
$list.on("change", ".genius-version-select", function (event) {
event.stopPropagation();
var val = $(this).val();
if (val === "") return;
patchVersion($(this).data("key"), val);
});
/* search + filter chips */
$("#searchInput").on("input", function () {
state.search = this.value.toLowerCase();
renderList();
});
$(".genius-chip").on("click", function () {
$(".genius-chip").removeClass("is-active");
$(this).addClass("is-active");
state.filter = $(this).data("filter");
renderList();
});
/* buttons + shortcut */
$("#save-json").on("click", saveJson);
$("#format-json").on("click", formatJson);
document.addEventListener("keydown", function (event) {
if ((event.key === "s" || event.key === "S") && (event.ctrlKey || event.metaKey)) {
event.preventDefault();
saveJson();
}
});
/* ---------- data loading ---------- */
function loadComponents(done) {
$.ajax({
url: cfg.ajax_url,
method: "POST",
data: { action: cfg.components_action || "get_genius_components", nonce: cfg.nonce },
success: function (response) {
if (response && response.success && response.data) {
state.components = response.data.components || [];
state.schema = response.data.schema || null;
updateCounts();
renderList();
} else {
$list.html('<p class="genius-empty">Could not load components.</p>');
}
if (done) done();
},
error: function () {
$list.html('<p class="genius-empty">Could not load components.</p>');
if (done) done();
},
});
}
function loadJsonContent() {
var jsonUrl = cfg.json_file_url + "?t=" + new Date().getTime();
$.get(jsonUrl, function (data) {
var text = typeof data === "string" ? data : JSON.stringify(data, null, 4);
try {
text = JSON.stringify(JSON.parse(text), null, 4);
} catch (e) {
/* leave raw if not valid JSON */
}
setValue(text);
$ta.val(getValue());
if (state.mode === "monaco" && state.editor) {
setTimeout(function () {
// Keep the root expanded, fold its children to one line each.
state.editor.setPosition({ lineNumber: 1, column: 1 });
var fold = state.editor.getAction("editor.foldLevel1");
if (fold) fold.run();
state.editor.revealLine(1);
}, 300);
}
}).fail(function () {
showToast("Error loading JSON file", "error");
setStatus("Could not load genius.json", "error");
});
}
function applySchema() {
if (!window.monaco || !state.schema) return;
try {
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
validate: true,
allowComments: false,
enableSchemaRequest: false,
schemas: [
{
uri: "inmemory://leadbox/genius-schema.json",
fileMatch: ["*"],
schema: state.schema,
},
],
});
} catch (e) {
/* schema is a nice-to-have; never block the editor */
}
}
/* ---------- editors ---------- */
function initMonaco() {
try {
state.editor = monaco.editor.create(document.getElementById("json-editor"), {
value: "",
language: "json",
theme: "vs-dark",
automaticLayout: true,
fontSize: 13,
tabSize: 4,
insertSpaces: true,
minimap: { enabled: true },
scrollBeyondLastLine: false,
formatOnPaste: true,
fixedOverflowWidgets: true,
});
state.mode = "monaco";
} catch (e) {
console.error("Monaco init failed, using fallback editor", e);
initFallback();
return;
}
loadComponents(function () {
applySchema();
loadJsonContent();
});
}
function initFallback() {
state.mode = "fallback";
var $editor = $("#json-editor");
$editor.addClass("genius-fallback").empty();
state._fallbackArea = $('<textarea class="genius-fallback-area" spellcheck="false"></textarea>');
$editor.append(state._fallbackArea);
loadComponents(function () {
loadJsonContent();
});
}
/* ---------- bootstrap ----------
* Monaco's AMD globals were stashed away (in PHP) right after loader.js so
* they don't clobber WordPress's underscore/backbone. We borrow them only
* for the duration of the load, then put the page's globals back. */
function loadMonaco() {
var amd = window.__leadboxMonacoAMD;
if (!amd || typeof amd.require === "undefined" || !vsPath) {
initFallback();
return;
}
// Hand Monaco back its AMD globals. We KEEP them in place on success:
// Monaco lazily loads language modes (jsonMode → syntax colors) and worker
// glue via define/require AFTER the editor is created, so removing them too
// early breaks coloring/validation. WordPress's UMD libs (underscore,
// backbone) already ran during page parse while these globals were
// neutralized, so keeping Monaco's loader now no longer affects them.
// Only on failure do we neutralize again, to leave the page clean.
var neutralRequire = window.require; // undefined (set by the inline shim)
var neutralDefine = window.define; // undefined
window.require = amd.require;
window.define = amd.define;
function neutralize() {
window.require = neutralRequire;
window.define = neutralDefine;
}
try {
window.MonacoEnvironment = {
getWorkerUrl: function () {
return (
"data:text/javascript;charset=utf-8," +
encodeURIComponent(
"self.MonacoEnvironment = { baseUrl: '" + baseUrl + "' };\n" +
"importScripts('" + vsPath + "/base/worker/workerMain.js');"
)
);
},
};
window.require.config({ paths: { vs: vsPath } });
window.require(
["vs/editor/editor.main"],
function () {
initMonaco();
},
function (err) {
neutralize();
console.error("Failed to load Monaco, using fallback editor", err);
initFallback();
}
);
} catch (e) {
neutralize();
console.error(e);
initFallback();
}
}
loadMonaco();
});