function loadJson() {
const jsonInput = document.getElementById('jsonInput').value;
const jsonData = JSON.parse(jsonInput);
try {
const html = generateHtmlFromSchema(jsonData);
document.getElementById('jsonTree').innerHTML = `
Data structure
` + html;
$('.selectable').popup({ hoverable: true, onShow: function (e) { popupFiller(this, e) } });
} catch (e) {
$.toast({
class: 'failure red',
position: 'bottom right',
message: `Something went wrong while parsing the schema: ${e.message}'.`
});
}
}
function popupFiller(popup, e) {
let selection = tinymce.activeEditor.selection;
let parentNodeText = '';
let parentNode = '';
if (selection.getRng().commonAncestorContainer.localName == 'tr' || selection.getRng().commonAncestorContainer.parentNode.localName == 'tr') {
// Table with row
parentNodeText = "row";
parentNode = 'tr';
} else if (selection.getRng().commonAncestorContainer.parentNode.localName == 'li') {
// Listitem
parentNodeText = "listitem";
parentNode = 'li';
} else {
parentNodeText = selection.getRng().commonAncestorContainer.parentNode.localName;
parentNode = selection.getRng().commonAncestorContainer.parentNode.localName;
}
const newpath = $(e).prev().data('newpath');
const type = $(e).prev().data('type');
const value = $(e).prev().data('value');
// onmousedown="selectNode('${newpath}', '${value}', '${type}');"
const html = `
${selection.getContent()}
Choose
`;
$(popup).html(html);
}
function isValidPath(path) {
let validPath = false;
if (!path.includes("[]")) {
validPath = true;
} else if (path.endsWith("[]") && path.split("[]").length - 1 == 1) {
validPath = true;
} else {
const selection = tinymce.activeEditor.selection;
// Check valid path
const selectedNode = selection.getNode();
let nodesToCheck = [selectedNode];
const parentsToCheck = tinymce.DOM.getParents(selectedNode, '[data-json-path*="[]"]');
nodesToCheck = nodesToCheck.concat(parentsToCheck);
for (node in nodesToCheck) {
if (nodesToCheck[node].getAttribute('data-json-path') == path.substring(0, path.lastIndexOf('.')).toString()) {
validPath = true;
}
}
//console.log(tinymce.DOM.getParents(selectedNode, '[data-json-path~="[]"]'));
}
return validPath;
}
function selectNode(path, value, type, node) {
if (isValidPath(path)) {
const selection = tinymce.activeEditor.selection;
if (type == "array" || type == 'boolean') {
if (!selection.isCollapsed()) {
if (node == 'selection') {
// Text selection
const span = document.createElement('span');
span.setAttribute('data-json-path', path);
span.innerHTML = selection.getContent();
console.log(selection.getContent())
selection.setContent(span.outerHTML);
} else {
// Node selection
if (selection.getRng().commonAncestorContainer.localName == 'tr') {
selection.getRng().commonAncestorContainer.setAttribute('data-json-path', path);
} else if (selection.getRng().commonAncestorContainer.parentNode.localName == 'tr') {
selection.getRng().commonAncestorContainer.parentNode.setAttribute('data-json-path', path);
} else {
selection.getRng().commonAncestorContainer.parentNode.setAttribute('data-json-path', path);
}
}
} else {
// No specific selection, just note the array
$.toast({
class: 'failure orange',
position: 'bottom right',
message: `Select a block or text in the editor to link this array.`
});
}
} else {
// Insert value as span
if (!selection.isCollapsed()) {
// Replace selected text
const span = document.createElement('span');
span.textContent = value;
span.setAttribute('data-json-path', path);
span.setAttribute('contenteditable', 'false');
selection.setContent(span.outerHTML);
} else {
// Insert at cursor position or at the end
const span = document.createElement('span');
span.textContent = value;
span.setAttribute('data-json-path', path);
span.setAttribute('contenteditable', 'false');
selection.setContent(span.outerHTML);
}
}
} else {
$.toast({
class: 'failure red',
position: 'bottom right',
message: `Sorry this path cannot be chosen: ${path}`
});
}
}
function generateHtmlFromSchema(schema, nodeName = 'root', depth = 0, requiredFields = [], path = "") {
let newPath = path + (path ? '.' : '') + nodeName + (schema.type == 'array' ? "[]" : "");
const colors = {
object: 'blue',
array: 'green',
string: 'red',
number: 'orange',
integer: 'orange',
boolean: 'purple',
oneOf: 'cyan',
enum: 'magenta'
};
let html = ``;
if (schema.type === 'array') {
html += `${nodeName}`;
if (schema.items) {
if (schema.items.properties) {
html += generateHtmlFromSchema(Object.values(schema.items.properties)[0], Object.keys(schema.items.properties)[0], depth + 1, requiredFields, newPath);
} else {
html += generateHtmlFromSchema(schema.items, nodeName, depth + 1, requiredFields, newPath);
}
}
} else if (schema.type === 'object' && schema.properties) {
html += `${nodeName}`;
Object.keys(schema.properties).forEach(key => {
html += generateHtmlFromSchema(schema.properties[key], key, depth + 1, schema.required || [], newPath);
});
} else if (schema.type == 'boolean') {
html += `${nodeName}`;
} else {
// Primitive types
html += `${nodeName}`;
}
html += `
`;
return html;
}
function isValidScope(path, range) {
const commonAncestor = range.commonAncestorContainer;
let element = commonAncestor.nodeType === 3 ? commonAncestor.parentNode : commonAncestor;
// Initialize a flag to check if any data-json-path attributes exist in the editor
let anyDataJsonPathExists = document.querySelector('[data-json-path]') !== null;
let foundMatchingPath = false;
let isPathWithinArray = path.includes('[]');
while (element && element !== document) {
if (element.getAttribute && element.getAttribute('data-json-path')) {
const elementPath = element.getAttribute('data-json-path');
// Direct match or elementPath is a prefix of the path
if (path === elementPath || path.startsWith(elementPath + '.')) {
foundMatchingPath = true;
break; // Found a matching path, no need to go further
}
}
element = element.parentNode;
}
// If no data-json-path attributes exist in the editor, allow the insertion
// This is particularly relevant for the initial state or a blank editor
if (!anyDataJsonPathExists) {
return true;
}
// If a matching path was found, or if we're not dealing with an array context in a non-initial state, apply the original logic
return foundMatchingPath || (!foundMatchingPath && !isPathWithinArray);
}