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 = `

Repeat content

${selection.getContent()}

Choose

Repeat parent node

${parentNodeText}

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); }