Spaces:
Running
Running
🐳 10/02 - 14:47 - I should be able to star ta new json file by clcking anywhere on the left, and if it's an emptey file, will immediately pre-fille the opening { and open a key's editing textbox for
Browse files- index.html +12 -11
- script.js +137 -33
index.html
CHANGED
|
@@ -22,15 +22,15 @@
|
|
| 22 |
}
|
| 23 |
</script>
|
| 24 |
</head>
|
| 25 |
-
<body class="bg-slate-900
|
| 26 |
-
<div class="w-full">
|
| 27 |
-
<header class="mb-
|
| 28 |
<h1 class="text-2xl font-bold text-slate-100">Visual JSON Editor</h1>
|
| 29 |
<p class="text-slate-400 text-sm">The way JSON should <i class="italic font-semibold">ALWAYS</i> been editored!</p>
|
| 30 |
</header>
|
| 31 |
|
| 32 |
<!-- Menu Bar -->
|
| 33 |
-
<div class="menu-bar flex flex-wrap py-1 px-2">
|
| 34 |
<div class="dropdown">
|
| 35 |
<div class="menu-item px-2 py-1">File</div>
|
| 36 |
<div class="dropdown-content">
|
|
@@ -66,7 +66,7 @@
|
|
| 66 |
</div>
|
| 67 |
</div>
|
| 68 |
|
| 69 |
-
<div class="bg-slate-800 rounded-lg overflow-hidden mb-
|
| 70 |
<div class="p-2 bg-slate-700 border-b border-slate-600 flex flex-wrap items-center justify-between gap-2">
|
| 71 |
<div class="flex flex-wrap gap-1">
|
| 72 |
<button id="newBtn2" class="btn bg-primary hover:bg-secondary text-white px-3 py-1.5 rounded flex items-center gap-1 text-sm">
|
|
@@ -99,12 +99,12 @@
|
|
| 99 |
<div class="flex flex-col lg:flex-row gap-0.5">
|
| 100 |
<!-- Left Panel - Visual JSON Editor -->
|
| 101 |
<div class="w-full lg:w-1/2">
|
| 102 |
-
<div class="bg-slate-700 h-full flex flex-col">
|
| 103 |
<div class="flex justify-between items-center px-2 py-1 border-b border-slate-600">
|
| 104 |
<h2 class="text-sm font-semibold text-slate-200">Visual Editor</h2>
|
| 105 |
-
<span class="text-xs text-slate-400">Click to edit • Tab
|
| 106 |
</div>
|
| 107 |
-
<div class="relative json-editor border-none overflow-auto flex-1" style="height:
|
| 108 |
<!-- Left line numbers -->
|
| 109 |
<div id="lineNumbersLeft" class="absolute left-0 top-0 bottom-0 w-8 bg-slate-800 text-slate-500 text-xs font-mono text-right pr-2 select-none overflow-hidden z-10 pt-1 border-r border-slate-700"></div>
|
| 110 |
<!-- Right line numbers -->
|
|
@@ -119,7 +119,7 @@
|
|
| 119 |
|
| 120 |
<!-- Right Panel - JSON Code Editor -->
|
| 121 |
<div class="w-full lg:w-1/2">
|
| 122 |
-
<div class="bg-gray-50 h-full flex flex-col">
|
| 123 |
<div class="flex justify-between items-center px-2 py-1 border-b border-gray-300 bg-gray-100">
|
| 124 |
<h2 class="text-sm font-semibold text-gray-700">Code Editor</h2>
|
| 125 |
<div class="flex gap-1 items-center">
|
|
@@ -129,7 +129,7 @@
|
|
| 129 |
</button>
|
| 130 |
</div>
|
| 131 |
</div>
|
| 132 |
-
<textarea id="jsonOutput" class="w-full flex-1 font-mono text-sm p-1 border-none bg-white focus:outline-none resize-none" style="height:
|
| 133 |
<div class="px-2 py-1 border-t border-gray-300 flex flex-wrap gap-1 justify-between items-center bg-gray-100">
|
| 134 |
<div class="flex gap-1">
|
| 135 |
<button id="copyBtn" class="btn bg-gray-200 hover:bg-gray-300 px-2 py-1 rounded text-xs">
|
|
@@ -147,9 +147,10 @@
|
|
| 147 |
</div>
|
| 148 |
</div>
|
| 149 |
</div>
|
|
|
|
| 150 |
</div>
|
| 151 |
|
| 152 |
-
<div id="notification" class="notification fixed bottom-4 right-4 bg-white shadow-lg rounded-lg p-4 border-l-4 border-green-500 max-w-md">
|
| 153 |
<div class="flex items-start">
|
| 154 |
<i class="fas fa-check-circle text-green-500 text-xl mt-0.5 mr-3"></i>
|
| 155 |
<div>
|
|
|
|
| 22 |
}
|
| 23 |
</script>
|
| 24 |
</head>
|
| 25 |
+
<body class="bg-slate-900 h-screen overflow-hidden p-0">
|
| 26 |
+
<div class="w-full h-screen flex flex-col">
|
| 27 |
+
<header class="flex-shrink-0 mb-1 text-center py-1">
|
| 28 |
<h1 class="text-2xl font-bold text-slate-100">Visual JSON Editor</h1>
|
| 29 |
<p class="text-slate-400 text-sm">The way JSON should <i class="italic font-semibold">ALWAYS</i> been editored!</p>
|
| 30 |
</header>
|
| 31 |
|
| 32 |
<!-- Menu Bar -->
|
| 33 |
+
<div class="menu-bar flex flex-wrap py-1 px-2 flex-shrink-0">
|
| 34 |
<div class="dropdown">
|
| 35 |
<div class="menu-item px-2 py-1">File</div>
|
| 36 |
<div class="dropdown-content">
|
|
|
|
| 66 |
</div>
|
| 67 |
</div>
|
| 68 |
|
| 69 |
+
<div class="bg-slate-800 rounded-lg overflow-hidden mb-1 border border-slate-700 flex-1 flex flex-col overflow-hidden">
|
| 70 |
<div class="p-2 bg-slate-700 border-b border-slate-600 flex flex-wrap items-center justify-between gap-2">
|
| 71 |
<div class="flex flex-wrap gap-1">
|
| 72 |
<button id="newBtn2" class="btn bg-primary hover:bg-secondary text-white px-3 py-1.5 rounded flex items-center gap-1 text-sm">
|
|
|
|
| 99 |
<div class="flex flex-col lg:flex-row gap-0.5">
|
| 100 |
<!-- Left Panel - Visual JSON Editor -->
|
| 101 |
<div class="w-full lg:w-1/2">
|
| 102 |
+
<div class="bg-slate-700 h-full flex flex-col" style="height: calc(100vh - 120px);">
|
| 103 |
<div class="flex justify-between items-center px-2 py-1 border-b border-slate-600">
|
| 104 |
<h2 class="text-sm font-semibold text-slate-200">Visual Editor</h2>
|
| 105 |
+
<span class="text-xs text-slate-400">Click to edit • Tab nav • Ctrl+Tab indent</span>
|
| 106 |
</div>
|
| 107 |
+
<div class="relative json-editor border-none overflow-auto flex-1" style="min-height: 100%;">
|
| 108 |
<!-- Left line numbers -->
|
| 109 |
<div id="lineNumbersLeft" class="absolute left-0 top-0 bottom-0 w-8 bg-slate-800 text-slate-500 text-xs font-mono text-right pr-2 select-none overflow-hidden z-10 pt-1 border-r border-slate-700"></div>
|
| 110 |
<!-- Right line numbers -->
|
|
|
|
| 119 |
|
| 120 |
<!-- Right Panel - JSON Code Editor -->
|
| 121 |
<div class="w-full lg:w-1/2">
|
| 122 |
+
<div class="bg-gray-50 h-full flex flex-col" style="height: calc(100vh - 120px);">
|
| 123 |
<div class="flex justify-between items-center px-2 py-1 border-b border-gray-300 bg-gray-100">
|
| 124 |
<h2 class="text-sm font-semibold text-gray-700">Code Editor</h2>
|
| 125 |
<div class="flex gap-1 items-center">
|
|
|
|
| 129 |
</button>
|
| 130 |
</div>
|
| 131 |
</div>
|
| 132 |
+
<textarea id="jsonOutput" class="w-full flex-1 font-mono text-sm p-1 border-none bg-white focus:outline-none resize-none" style="min-height: 100%;"></textarea>
|
| 133 |
<div class="px-2 py-1 border-t border-gray-300 flex flex-wrap gap-1 justify-between items-center bg-gray-100">
|
| 134 |
<div class="flex gap-1">
|
| 135 |
<button id="copyBtn" class="btn bg-gray-200 hover:bg-gray-300 px-2 py-1 rounded text-xs">
|
|
|
|
| 147 |
</div>
|
| 148 |
</div>
|
| 149 |
</div>
|
| 150 |
+
>
|
| 151 |
</div>
|
| 152 |
|
| 153 |
+
<div id="notification" class="notification fixed bottom-4 right-4 bg-white shadow-lg rounded-lg p-4 border-l-4 border-green-500 max-w-md z-50">
|
| 154 |
<div class="flex items-start">
|
| 155 |
<i class="fas fa-check-circle text-green-500 text-xl mt-0.5 mr-3"></i>
|
| 156 |
<div>
|
script.js
CHANGED
|
@@ -78,6 +78,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
| 78 |
// Initialize with sample data
|
| 79 |
loadJSON(sampleJSON);
|
| 80 |
|
|
|
|
|
|
|
|
|
|
| 81 |
// Create hidden file input for opening files
|
| 82 |
const fileInput = document.createElement('input');
|
| 83 |
fileInput.type = 'file';
|
|
@@ -252,9 +255,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
| 252 |
// Load JSON data into the editor
|
| 253 |
function loadJSON(data) {
|
| 254 |
jsonData = JSON.parse(JSON.stringify(data));
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
renderEditor();
|
| 256 |
updateOutput();
|
| 257 |
-
saveToHistory();
|
| 258 |
}
|
| 259 |
|
| 260 |
// Render the JSON editor
|
|
@@ -645,11 +651,22 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
| 645 |
input.addEventListener('keydown', (e) => {
|
| 646 |
if (e.key === 'Enter') {
|
| 647 |
e.preventDefault();
|
| 648 |
-
|
|
|
|
|
|
|
|
|
|
| 649 |
} else if (e.key === 'Tab') {
|
| 650 |
e.preventDefault();
|
| 651 |
-
|
| 652 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 653 |
} else if (e.shiftKey && e.key === 'Enter') {
|
| 654 |
e.preventDefault();
|
| 655 |
saveEdit();
|
|
@@ -663,7 +680,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
| 663 |
}
|
| 664 |
|
| 665 |
// Navigate to next/previous editable field
|
| 666 |
-
function navigateFields(direction) {
|
| 667 |
// Find all editable spans
|
| 668 |
const keys = jsonEditor.querySelectorAll('.json-key');
|
| 669 |
const values = jsonEditor.querySelectorAll('.json-value');
|
|
@@ -682,6 +699,25 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
| 682 |
}
|
| 683 |
|
| 684 |
let nextIndex = currentIndex + direction;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 685 |
if (nextIndex < 0) nextIndex = allFields.length - 1;
|
| 686 |
if (nextIndex >= allFields.length) nextIndex = 0;
|
| 687 |
|
|
@@ -690,6 +726,63 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
| 690 |
}
|
| 691 |
}
|
| 692 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 693 |
// Get nested value
|
| 694 |
function getValue(parentKey, key) {
|
| 695 |
if (parentKey === 'root') {
|
|
@@ -752,32 +845,22 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
| 752 |
// Mark this as a new field for tracking
|
| 753 |
newKey = newKey + '_new';
|
| 754 |
|
| 755 |
-
|
| 756 |
-
|
| 757 |
-
|
| 758 |
-
|
| 759 |
-
if (
|
| 760 |
-
|
| 761 |
-
jsonData[newKey] = '';
|
| 762 |
-
} else {
|
| 763 |
-
// Insert after current - need to reconstruct
|
| 764 |
-
const newData = {};
|
| 765 |
-
keys.forEach((k, i) => {
|
| 766 |
-
newData[k] = jsonData[k];
|
| 767 |
-
if (i === index) {
|
| 768 |
-
newData[newKey] = '';
|
| 769 |
-
}
|
| 770 |
-
});
|
| 771 |
-
Object.keys(jsonData).forEach(k => delete jsonData[k]);
|
| 772 |
-
Object.assign(jsonData, newData);
|
| 773 |
}
|
| 774 |
-
|
| 775 |
-
|
| 776 |
-
if (
|
|
|
|
|
|
|
| 777 |
const keys = Object.keys(parent);
|
| 778 |
const index = keys.indexOf(key);
|
| 779 |
|
| 780 |
-
if (index === -1 || index === keys.length - 1) {
|
| 781 |
// Append at the end
|
| 782 |
parent[newKey] = '';
|
| 783 |
} else {
|
|
@@ -792,9 +875,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
| 792 |
Object.keys(parent).forEach(k => delete parent[k]);
|
| 793 |
Object.assign(parent, newData);
|
| 794 |
}
|
| 795 |
-
} else if (parent && Array.isArray(parent)) {
|
| 796 |
-
// For arrays, push new empty string
|
| 797 |
-
parent.push('');
|
| 798 |
}
|
| 799 |
}
|
| 800 |
|
|
@@ -802,6 +882,17 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
| 802 |
updateOutput();
|
| 803 |
saveToHistory();
|
| 804 |
showSavedIndicator();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 805 |
}
|
| 806 |
|
| 807 |
// Parse a value from string to appropriate type
|
|
@@ -1076,8 +1167,19 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
| 1076 |
jsonEditor.addEventListener('click', (e) => {
|
| 1077 |
// Only if clicking directly on json-editor (not on an item)
|
| 1078 |
if (e.target === jsonEditor) {
|
| 1079 |
-
//
|
| 1080 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1081 |
}
|
| 1082 |
});
|
| 1083 |
|
|
@@ -1119,7 +1221,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
| 1119 |
historyIndex--;
|
| 1120 |
}
|
| 1121 |
|
| 1122 |
-
|
|
|
|
|
|
|
| 1123 |
return;
|
| 1124 |
}
|
| 1125 |
|
|
|
|
| 78 |
// Initialize with sample data
|
| 79 |
loadJSON(sampleJSON);
|
| 80 |
|
| 81 |
+
// Set initial history state
|
| 82 |
+
saveToHistory();
|
| 83 |
+
|
| 84 |
// Create hidden file input for opening files
|
| 85 |
const fileInput = document.createElement('input');
|
| 86 |
fileInput.type = 'file';
|
|
|
|
| 255 |
// Load JSON data into the editor
|
| 256 |
function loadJSON(data) {
|
| 257 |
jsonData = JSON.parse(JSON.stringify(data));
|
| 258 |
+
// Initialize empty object if data is null/undefined
|
| 259 |
+
if (!jsonData || (typeof jsonData === 'object' && Object.keys(jsonData).length === 0)) {
|
| 260 |
+
jsonData = {};
|
| 261 |
+
}
|
| 262 |
renderEditor();
|
| 263 |
updateOutput();
|
|
|
|
| 264 |
}
|
| 265 |
|
| 266 |
// Render the JSON editor
|
|
|
|
| 651 |
input.addEventListener('keydown', (e) => {
|
| 652 |
if (e.key === 'Enter') {
|
| 653 |
e.preventDefault();
|
| 654 |
+
// Save current edit
|
| 655 |
+
saveEdit();
|
| 656 |
+
// Move to next field
|
| 657 |
+
navigateFields(1, true); // true = auto-add comma/newline
|
| 658 |
} else if (e.key === 'Tab') {
|
| 659 |
e.preventDefault();
|
| 660 |
+
// Check for Ctrl+Tab or Ctrl+Shift+Tab for indentation
|
| 661 |
+
if (e.ctrlKey) {
|
| 662 |
+
// Change indentation depth
|
| 663 |
+
const direction = e.shiftKey ? -1 : 1;
|
| 664 |
+
changeIndentation(key, parentKey, direction);
|
| 665 |
+
} else {
|
| 666 |
+
// Normal tab navigation
|
| 667 |
+
const direction = e.shiftKey ? -1 : 1;
|
| 668 |
+
navigateFields(direction, false);
|
| 669 |
+
}
|
| 670 |
} else if (e.shiftKey && e.key === 'Enter') {
|
| 671 |
e.preventDefault();
|
| 672 |
saveEdit();
|
|
|
|
| 680 |
}
|
| 681 |
|
| 682 |
// Navigate to next/previous editable field
|
| 683 |
+
function navigateFields(direction, autoAddField = false) {
|
| 684 |
// Find all editable spans
|
| 685 |
const keys = jsonEditor.querySelectorAll('.json-key');
|
| 686 |
const values = jsonEditor.querySelectorAll('.json-value');
|
|
|
|
| 699 |
}
|
| 700 |
|
| 701 |
let nextIndex = currentIndex + direction;
|
| 702 |
+
|
| 703 |
+
// If we're on the last field and going forward, add a new field first
|
| 704 |
+
if (autoAddField && nextIndex >= allFields.length) {
|
| 705 |
+
// Find current field's info
|
| 706 |
+
if (currentIndex >= 0) {
|
| 707 |
+
const currentField = allFields[currentIndex];
|
| 708 |
+
const parentElement = currentField.element.closest('.json-item');
|
| 709 |
+
if (parentElement) {
|
| 710 |
+
insertNewField({
|
| 711 |
+
key: parentElement.dataset.key,
|
| 712 |
+
parentKey: parentElement.dataset.parent
|
| 713 |
+
});
|
| 714 |
+
// Re-render and navigate to the new field
|
| 715 |
+
setTimeout(() => navigateFields(direction, false), 10);
|
| 716 |
+
return;
|
| 717 |
+
}
|
| 718 |
+
}
|
| 719 |
+
}
|
| 720 |
+
|
| 721 |
if (nextIndex < 0) nextIndex = allFields.length - 1;
|
| 722 |
if (nextIndex >= allFields.length) nextIndex = 0;
|
| 723 |
|
|
|
|
| 726 |
}
|
| 727 |
}
|
| 728 |
|
| 729 |
+
// Change indentation depth of a field
|
| 730 |
+
function changeIndentation(key, parentKey, direction) {
|
| 731 |
+
// direction: 1 = indent deeper, -1 = un-indent
|
| 732 |
+
|
| 733 |
+
const currentValue = getValue(parentKey, key);
|
| 734 |
+
if (currentValue === undefined) return;
|
| 735 |
+
|
| 736 |
+
if (parentKey === 'root') {
|
| 737 |
+
// Can't un-indent from root
|
| 738 |
+
if (direction === -1) {
|
| 739 |
+
showNotification('Already at root level', true);
|
| 740 |
+
return;
|
| 741 |
+
}
|
| 742 |
+
// Need to create a wrapper object to indent
|
| 743 |
+
const wrapperKey = 'wrapper';
|
| 744 |
+
const newData = { [wrapperKey]: { [key]: currentValue } };
|
| 745 |
+
delete jsonData[key];
|
| 746 |
+
jsonData[wrapperKey] = { [key]: currentValue };
|
| 747 |
+
} else {
|
| 748 |
+
const parent = findElementByKey(jsonData, parentKey);
|
| 749 |
+
const grandParent = findElementByKey(jsonData, parentKey === 'root' ? 'root' :
|
| 750 |
+
parent?.__parentKey || 'root');
|
| 751 |
+
|
| 752 |
+
if (direction === 1) {
|
| 753 |
+
// Indent deeper - create wrapper in parent
|
| 754 |
+
if (parent && typeof parent === 'object' && !Array.isArray(parent)) {
|
| 755 |
+
const wrapperKey = 'wrapper';
|
| 756 |
+
const oldObj = { [key]: currentValue };
|
| 757 |
+
delete parent[key];
|
| 758 |
+
parent[wrapperKey] = oldObj;
|
| 759 |
+
}
|
| 760 |
+
} else {
|
| 761 |
+
// Un-indent - move up one level
|
| 762 |
+
if (parent) {
|
| 763 |
+
const parentOfParent = findElementByKey(jsonData, parentKey === 'root' ? 'root' :
|
| 764 |
+
parent?.__parentKey || 'root');
|
| 765 |
+
|
| 766 |
+
if (parentOfParent && parentOfParent !== jsonData) {
|
| 767 |
+
// Move to grandparent
|
| 768 |
+
delete parent[key];
|
| 769 |
+
parentOfParent[key] = currentValue;
|
| 770 |
+
|
| 771 |
+
// Clean up empty parent
|
| 772 |
+
if (Object.keys(parent).length === 0) {
|
| 773 |
+
removeFromParent(parentKey === 'root' ? 'root' : parent?.__parentKey || 'root', parentKey);
|
| 774 |
+
}
|
| 775 |
+
}
|
| 776 |
+
}
|
| 777 |
+
}
|
| 778 |
+
}
|
| 779 |
+
|
| 780 |
+
renderEditor();
|
| 781 |
+
updateOutput();
|
| 782 |
+
saveToHistory();
|
| 783 |
+
showSavedIndicator();
|
| 784 |
+
}
|
| 785 |
+
|
| 786 |
// Get nested value
|
| 787 |
function getValue(parentKey, key) {
|
| 788 |
if (parentKey === 'root') {
|
|
|
|
| 845 |
// Mark this as a new field for tracking
|
| 846 |
newKey = newKey + '_new';
|
| 847 |
|
| 848 |
+
const parent = parentKey === 'root' ? jsonData : findElementByKey(jsonData, parentKey);
|
| 849 |
+
|
| 850 |
+
if (!parent) {
|
| 851 |
+
// If parent doesn't exist, initialize jsonData as empty object
|
| 852 |
+
if (typeof jsonData !== 'object' || jsonData === null) {
|
| 853 |
+
jsonData = {};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 854 |
}
|
| 855 |
+
jsonData[newKey] = '';
|
| 856 |
+
} else if (typeof parent === 'object' && parent !== null) {
|
| 857 |
+
if (Array.isArray(parent)) {
|
| 858 |
+
parent.push('');
|
| 859 |
+
} else {
|
| 860 |
const keys = Object.keys(parent);
|
| 861 |
const index = keys.indexOf(key);
|
| 862 |
|
| 863 |
+
if (index === -1 || index === keys.length - 1 || key === null) {
|
| 864 |
// Append at the end
|
| 865 |
parent[newKey] = '';
|
| 866 |
} else {
|
|
|
|
| 875 |
Object.keys(parent).forEach(k => delete parent[k]);
|
| 876 |
Object.assign(parent, newData);
|
| 877 |
}
|
|
|
|
|
|
|
|
|
|
| 878 |
}
|
| 879 |
}
|
| 880 |
|
|
|
|
| 882 |
updateOutput();
|
| 883 |
saveToHistory();
|
| 884 |
showSavedIndicator();
|
| 885 |
+
|
| 886 |
+
// Auto-focus the new field after rendering
|
| 887 |
+
setTimeout(() => {
|
| 888 |
+
const newItems = jsonEditor.querySelectorAll('.json-item');
|
| 889 |
+
newItems.forEach(item => {
|
| 890 |
+
if (item.dataset.key === newKey) {
|
| 891 |
+
const keySpan = item.querySelector('.json-key');
|
| 892 |
+
if (keySpan) keySpan.click();
|
| 893 |
+
}
|
| 894 |
+
});
|
| 895 |
+
}, 10);
|
| 896 |
}
|
| 897 |
|
| 898 |
// Parse a value from string to appropriate type
|
|
|
|
| 1167 |
jsonEditor.addEventListener('click', (e) => {
|
| 1168 |
// Only if clicking directly on json-editor (not on an item)
|
| 1169 |
if (e.target === jsonEditor) {
|
| 1170 |
+
// Check if editor is empty (only contains closing bracket)
|
| 1171 |
+
const isEmpty = jsonEditor.innerHTML.trim() === '' ||
|
| 1172 |
+
(jsonEditor.children.length === 1 &&
|
| 1173 |
+
jsonEditor.children[0].textContent.includes('}'));
|
| 1174 |
+
|
| 1175 |
+
if (isEmpty) {
|
| 1176 |
+
// Initialize new JSON structure
|
| 1177 |
+
jsonData = {};
|
| 1178 |
+
insertNewField({ key: null, parentKey: 'root' });
|
| 1179 |
+
} else {
|
| 1180 |
+
// Insert at root level
|
| 1181 |
+
insertNewField({ key: null, parentKey: 'root' });
|
| 1182 |
+
}
|
| 1183 |
}
|
| 1184 |
});
|
| 1185 |
|
|
|
|
| 1221 |
historyIndex--;
|
| 1222 |
}
|
| 1223 |
|
| 1224 |
+
// Fix: Check if history has content before comparing
|
| 1225 |
+
if (history.length > 0 && historyIndex >= 0 &&
|
| 1226 |
+
JSON.stringify(history[historyIndex]) === JSON.stringify(jsonData)) {
|
| 1227 |
return;
|
| 1228 |
}
|
| 1229 |
|