Spaces:
Running
Running
Make boolean choices selectable combo-boxes; drop-down can be changed with up or down arrows, but can also be changed to text elements by typing into the combo box. Make the rendering more like a visual flow rendering and less techie looking, showing indent levels more clearly less reliant on the brackets and more like graphical elements. Make the background a dark backgrounf with a faint grid or dot-grid . Make the instructions available from the File Edit View Tools Help menu and instead of starting with demo content, start blank, but add the demo content to the Help menu and the instructions . Use a split-screen format by default, where pasting into either side pastes json to one side and 'visual graph/flow' on the other - Initial Deployment
Browse files- README.md +7 -5
- index.html +729 -19
README.md
CHANGED
@@ -1,10 +1,12 @@
|
|
1 |
---
|
2 |
-
title:
|
3 |
-
emoji:
|
4 |
-
colorFrom:
|
5 |
-
colorTo:
|
6 |
sdk: static
|
7 |
pinned: false
|
|
|
|
|
8 |
---
|
9 |
|
10 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
1 |
---
|
2 |
+
title: jsonic-v2
|
3 |
+
emoji: 🐳
|
4 |
+
colorFrom: pink
|
5 |
+
colorTo: green
|
6 |
sdk: static
|
7 |
pinned: false
|
8 |
+
tags:
|
9 |
+
- deepsite
|
10 |
---
|
11 |
|
12 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
index.html
CHANGED
@@ -1,19 +1,729 @@
|
|
1 |
-
<!
|
2 |
-
<html>
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>Visual JSON Editor</title>
|
7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
8 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
9 |
+
<script>
|
10 |
+
tailwind.config = {
|
11 |
+
theme: {
|
12 |
+
extend: {
|
13 |
+
colors: {
|
14 |
+
primary: '#3b82f6',
|
15 |
+
secondary: '#1e40af',
|
16 |
+
dark: '#0f172a',
|
17 |
+
light: '#f8fafc'
|
18 |
+
}
|
19 |
+
}
|
20 |
+
}
|
21 |
+
}
|
22 |
+
</script>
|
23 |
+
<style>
|
24 |
+
.json-editor {
|
25 |
+
min-height: 500px;
|
26 |
+
max-height: 70vh;
|
27 |
+
overflow-y: auto;
|
28 |
+
}
|
29 |
+
.json-item {
|
30 |
+
transition: all 0.2s ease;
|
31 |
+
border-left: 3px solid transparent;
|
32 |
+
}
|
33 |
+
.json-item:hover {
|
34 |
+
background-color: #f1f5f9;
|
35 |
+
border-left: 3px solid #3b82f6;
|
36 |
+
}
|
37 |
+
.json-item.selected {
|
38 |
+
background-color: #dbeafe;
|
39 |
+
border-left: 3px solid #3b82f6;
|
40 |
+
}
|
41 |
+
.json-item.dragging {
|
42 |
+
opacity: 0.5;
|
43 |
+
background-color: #dbeafe;
|
44 |
+
}
|
45 |
+
.json-item.drag-over {
|
46 |
+
border-top: 2px dashed #3b82f6;
|
47 |
+
}
|
48 |
+
.json-key {
|
49 |
+
font-weight: 600;
|
50 |
+
color: #4338ca;
|
51 |
+
}
|
52 |
+
.json-value {
|
53 |
+
color: #0f766e;
|
54 |
+
}
|
55 |
+
.json-bracket {
|
56 |
+
color: #64748b;
|
57 |
+
}
|
58 |
+
.btn {
|
59 |
+
transition: all 0.2s ease;
|
60 |
+
}
|
61 |
+
.btn:hover {
|
62 |
+
transform: translateY(-2px);
|
63 |
+
}
|
64 |
+
.indent-line {
|
65 |
+
position: absolute;
|
66 |
+
left: 0;
|
67 |
+
top: 0;
|
68 |
+
bottom: 0;
|
69 |
+
width: 1px;
|
70 |
+
background-color: #cbd5e1;
|
71 |
+
}
|
72 |
+
.cursor-pointer {
|
73 |
+
cursor: pointer;
|
74 |
+
}
|
75 |
+
.editable:focus {
|
76 |
+
outline: 2px solid #3b82f6;
|
77 |
+
border-radius: 4px;
|
78 |
+
}
|
79 |
+
.editable {
|
80 |
+
min-width: 20px;
|
81 |
+
display: inline-block;
|
82 |
+
}
|
83 |
+
.toolbar-btn {
|
84 |
+
transition: all 0.2s;
|
85 |
+
}
|
86 |
+
.toolbar-btn:hover {
|
87 |
+
background-color: #e2e8f0;
|
88 |
+
}
|
89 |
+
.toolbar-btn.active {
|
90 |
+
background-color: #3b82f6;
|
91 |
+
color: white;
|
92 |
+
}
|
93 |
+
.notification {
|
94 |
+
transform: translateX(100%);
|
95 |
+
transition: transform 0.3s ease;
|
96 |
+
}
|
97 |
+
.notification.show {
|
98 |
+
transform: translateX(0);
|
99 |
+
}
|
100 |
+
.error-highlight {
|
101 |
+
background-color: #fee2e2;
|
102 |
+
border-left: 3px solid #ef4444;
|
103 |
+
}
|
104 |
+
</style>
|
105 |
+
</head>
|
106 |
+
<body class="bg-gray-100 min-h-screen p-4 md:p-8">
|
107 |
+
<div class="max-w-6xl mx-auto">
|
108 |
+
<header class="mb-8 text-center">
|
109 |
+
<h1 class="text-3xl md:text-4xl font-bold text-gray-800 mb-2">Visual JSON Editor</h1>
|
110 |
+
<p class="text-gray-600">Edit JSON with drag-and-drop and intuitive keyboard navigation</p>
|
111 |
+
</header>
|
112 |
+
|
113 |
+
<div class="bg-white rounded-xl shadow-lg overflow-hidden mb-8">
|
114 |
+
<div class="p-4 bg-gray-50 border-b flex flex-wrap items-center justify-between gap-2">
|
115 |
+
<div class="flex flex-wrap gap-2">
|
116 |
+
<button id="newBtn" class="btn bg-primary hover:bg-secondary text-white px-4 py-2 rounded-lg flex items-center gap-2">
|
117 |
+
<i class="fas fa-plus"></i> New
|
118 |
+
</button>
|
119 |
+
<button id="loadBtn" class="btn bg-gray-200 hover:bg-gray-300 px-4 py-2 rounded-lg flex items-center gap-2">
|
120 |
+
<i class="fas fa-folder-open"></i> Load
|
121 |
+
</button>
|
122 |
+
<button id="saveBtn" class="btn bg-gray-200 hover:bg-gray-300 px-4 py-2 rounded-lg flex items-center gap-2">
|
123 |
+
<i class="fas fa-save"></i> Save
|
124 |
+
</button>
|
125 |
+
</div>
|
126 |
+
|
127 |
+
<div class="flex flex-wrap gap-2">
|
128 |
+
<button id="undoBtn" class="btn bg-gray-200 hover:bg-gray-300 px-3 py-2 rounded-lg">
|
129 |
+
<i class="fas fa-undo"></i>
|
130 |
+
</button>
|
131 |
+
<button id="redoBtn" class="btn bg-gray-200 hover:bg-gray-300 px-3 py-2 rounded-lg">
|
132 |
+
<i class="fas fa-redo"></i>
|
133 |
+
</button>
|
134 |
+
<button id="formatBtn" class="btn bg-gray-200 hover:bg-gray-300 px-3 py-2 rounded-lg">
|
135 |
+
<i class="fas fa-indent"></i>
|
136 |
+
</button>
|
137 |
+
<button id="validateBtn" class="btn bg-gray-200 hover:bg-gray-300 px-3 py-2 rounded-lg">
|
138 |
+
<i class="fas fa-check-circle"></i>
|
139 |
+
</button>
|
140 |
+
</div>
|
141 |
+
</div>
|
142 |
+
|
143 |
+
<div class="p-4 bg-gray-50 border-b flex flex-wrap gap-2">
|
144 |
+
<div class="flex items-center gap-2 bg-white px-3 py-1 rounded-lg shadow-sm">
|
145 |
+
<span class="text-gray-600">Mode:</span>
|
146 |
+
<div class="flex gap-1">
|
147 |
+
<button id="viewModeBtn" class="toolbar-btn px-3 py-1 rounded-md">View</button>
|
148 |
+
<button id="editModeBtn" class="toolbar-btn px-3 py-1 rounded-md active">Edit</button>
|
149 |
+
</div>
|
150 |
+
</div>
|
151 |
+
|
152 |
+
<div class="flex items-center gap-2 bg-white px-3 py-1 rounded-lg shadow-sm">
|
153 |
+
<span class="text-gray-600">Indent:</span>
|
154 |
+
<div class="flex gap-1">
|
155 |
+
<button id="indentBtn" class="toolbar-btn px-3 py-1 rounded-md">2</button>
|
156 |
+
<button id="indentBtn4" class="toolbar-btn px-3 py-1 rounded-md">4</button>
|
157 |
+
</div>
|
158 |
+
</div>
|
159 |
+
</div>
|
160 |
+
|
161 |
+
<div class="p-4 flex flex-col md:flex-row gap-4">
|
162 |
+
<div class="w-full md:w-1/2">
|
163 |
+
<div class="bg-gray-50 rounded-lg p-4 mb-4">
|
164 |
+
<h2 class="text-lg font-semibold text-gray-700 mb-2">JSON Editor</h2>
|
165 |
+
<div id="jsonEditor" class="json-editor bg-white border rounded-lg p-4 font-mono min-h-[400px] relative">
|
166 |
+
<!-- JSON content will be rendered here -->
|
167 |
+
</div>
|
168 |
+
</div>
|
169 |
+
|
170 |
+
<div class="bg-gray-50 rounded-lg p-4">
|
171 |
+
<h2 class="text-lg font-semibold text-gray-700 mb-2">JSON Output</h2>
|
172 |
+
<textarea id="jsonOutput" class="w-full h-40 font-mono text-sm p-3 border rounded-lg bg-white" readonly></textarea>
|
173 |
+
<div class="mt-2 flex justify-between">
|
174 |
+
<button id="copyBtn" class="btn bg-gray-200 hover:bg-gray-300 px-4 py-2 rounded-lg">
|
175 |
+
<i class="fas fa-copy"></i> Copy JSON
|
176 |
+
</button>
|
177 |
+
<button id="downloadBtn" class="btn bg-primary hover:bg-secondary text-white px-4 py-2 rounded-lg">
|
178 |
+
<i class="fas fa-download"></i> Download
|
179 |
+
</button>
|
180 |
+
</div>
|
181 |
+
</div>
|
182 |
+
</div>
|
183 |
+
|
184 |
+
<div class="w-full md:w-1/2">
|
185 |
+
<div class="bg-gray-50 rounded-lg p-4 h-full">
|
186 |
+
<h2 class="text-lg font-semibold text-gray-700 mb-2">Instructions</h2>
|
187 |
+
<div class="bg-white border rounded-lg p-4 h-[400px] overflow-y-auto">
|
188 |
+
<ul class="space-y-3 text-gray-700">
|
189 |
+
<li class="flex items-start">
|
190 |
+
<i class="fas fa-mouse-pointer text-primary mt-1 mr-2"></i>
|
191 |
+
<div>
|
192 |
+
<span class="font-medium">Double-click</span> to edit any element
|
193 |
+
</div>
|
194 |
+
</li>
|
195 |
+
<li class="flex items-start">
|
196 |
+
<i class="fas fa-arrows-alt text-primary mt-1 mr-2"></i>
|
197 |
+
<div>
|
198 |
+
<span class="font-medium">Drag and drop</span> elements to reorganize
|
199 |
+
</div>
|
200 |
+
</li>
|
201 |
+
<li class="flex items-start">
|
202 |
+
<i class="fas fa-keyboard text-primary mt-1 mr-2"></i>
|
203 |
+
<div>
|
204 |
+
<span class="font-medium">Keyboard navigation:</span>
|
205 |
+
<ul class="ml-5 mt-1 space-y-1">
|
206 |
+
<li>• <span class="font-mono">Tab</span> / <span class="font-mono">Shift+Tab</span> to change indent level</li>
|
207 |
+
<li>• <span class="font-mono">Enter</span> to create new element at same level</li>
|
208 |
+
<li>• <span class="font-mono">Shift+Enter</span> to create child element</li>
|
209 |
+
<li>• <span class="font-mono">Arrow keys</span> to navigate between elements</li>
|
210 |
+
<li>• <span class="font-mono">Delete</span> to remove selected element</li>
|
211 |
+
</ul>
|
212 |
+
</div>
|
213 |
+
</li>
|
214 |
+
<li class="flex items-start">
|
215 |
+
<i class="fas fa-paste text-primary mt-1 mr-2"></i>
|
216 |
+
<div>
|
217 |
+
<span class="font-medium">Paste JSON</span> to automatically parse and render
|
218 |
+
</div>
|
219 |
+
</li>
|
220 |
+
<li class="flex items-start">
|
221 |
+
<i class="fas fa-wrench text-primary mt-1 mr-2"></i>
|
222 |
+
<div>
|
223 |
+
<span class="font-medium">Auto-repair</span> fixes common JSON syntax errors
|
224 |
+
</div>
|
225 |
+
</li>
|
226 |
+
</ul>
|
227 |
+
|
228 |
+
<div class="mt-6 p-4 bg-blue-50 rounded-lg border border-blue-200">
|
229 |
+
<h3 class="font-semibold text-blue-800 mb-2">Try this sample JSON:</h3>
|
230 |
+
<pre class="text-sm bg-white p-3 rounded overflow-x-auto">{
|
231 |
+
"name": "John Doe",
|
232 |
+
"age": 30,
|
233 |
+
"address": {
|
234 |
+
"street": "123 Main St",
|
235 |
+
"city": "Anytown"
|
236 |
+
},
|
237 |
+
"hobbies": [
|
238 |
+
"reading",
|
239 |
+
"swimming"
|
240 |
+
]
|
241 |
+
}</pre>
|
242 |
+
<button id="loadSampleBtn" class="mt-3 btn bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg">
|
243 |
+
Load Sample
|
244 |
+
</button>
|
245 |
+
</div>
|
246 |
+
</div>
|
247 |
+
</div>
|
248 |
+
</div>
|
249 |
+
</div>
|
250 |
+
</div>
|
251 |
+
|
252 |
+
<div class="bg-white rounded-xl shadow-lg p-6">
|
253 |
+
<h2 class="text-xl font-bold text-gray-800 mb-4">How It Works</h2>
|
254 |
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
255 |
+
<div class="bg-blue-50 p-4 rounded-lg border border-blue-200">
|
256 |
+
<div class="text-blue-500 text-2xl mb-2">
|
257 |
+
<i class="fas fa-magic"></i>
|
258 |
+
</div>
|
259 |
+
<h3 class="font-bold text-lg mb-2">Visual Editing</h3>
|
260 |
+
<p class="text-gray-700">Easily edit JSON without worrying about brackets or indentation. Each element is visually represented for intuitive editing.</p>
|
261 |
+
</div>
|
262 |
+
<div class="bg-green-50 p-4 rounded-lg border border-green-200">
|
263 |
+
<div class="text-green-500 text-2xl mb-2">
|
264 |
+
<i class="fas fa-sync-alt"></i>
|
265 |
+
</div>
|
266 |
+
<h3 class="font-bold text-lg mb-2">Real-time Sync</h3>
|
267 |
+
<p class="text-gray-700">Changes in the visual editor are immediately reflected in the JSON output, and vice versa. Validate your JSON at any time.</p>
|
268 |
+
</div>
|
269 |
+
<div class="bg-purple-50 p-4 rounded-lg border border-purple-200">
|
270 |
+
<div class="text-purple-500 text-2xl mb-2">
|
271 |
+
<i class="fas fa-robot"></i>
|
272 |
+
</div>
|
273 |
+
<h3 class="font-bold text-lg mb-2">Auto-Repair</h3>
|
274 |
+
<p class="text-gray-700">Paste malformed JSON and our editor will attempt to automatically repair common syntax errors and structural issues.</p>
|
275 |
+
</div>
|
276 |
+
</div>
|
277 |
+
</div>
|
278 |
+
|
279 |
+
<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">
|
280 |
+
<div class="flex items-start">
|
281 |
+
<i class="fas fa-check-circle text-green-500 text-xl mt-0.5 mr-3"></i>
|
282 |
+
<div>
|
283 |
+
<h4 class="font-bold text-gray-800">Success!</h4>
|
284 |
+
<p class="text-gray-600 mt-1">Your JSON has been updated successfully.</p>
|
285 |
+
</div>
|
286 |
+
</div>
|
287 |
+
</div>
|
288 |
+
</div>
|
289 |
+
|
290 |
+
<script>
|
291 |
+
document.addEventListener('DOMContentLoaded', function() {
|
292 |
+
// Sample JSON data
|
293 |
+
const sampleJSON = {
|
294 |
+
"name": "John Doe",
|
295 |
+
"age": 30,
|
296 |
+
"isStudent": false,
|
297 |
+
"address": {
|
298 |
+
"street": "123 Main St",
|
299 |
+
"city": "Anytown",
|
300 |
+
"zipcode": "12345"
|
301 |
+
},
|
302 |
+
"hobbies": [
|
303 |
+
"reading",
|
304 |
+
"swimming",
|
305 |
+
"coding"
|
306 |
+
],
|
307 |
+
"contact": {
|
308 |
+
"email": "[email protected]",
|
309 |
+
"phone": "555-1234"
|
310 |
+
}
|
311 |
+
};
|
312 |
+
|
313 |
+
// DOM elements
|
314 |
+
const jsonEditor = document.getElementById('jsonEditor');
|
315 |
+
const jsonOutput = document.getElementById('jsonOutput');
|
316 |
+
const loadSampleBtn = document.getElementById('loadSampleBtn');
|
317 |
+
const notification = document.getElementById('notification');
|
318 |
+
const copyBtn = document.getElementById('copyBtn');
|
319 |
+
const downloadBtn = document.getElementById('downloadBtn');
|
320 |
+
|
321 |
+
// Current state
|
322 |
+
let jsonData = {};
|
323 |
+
let selectedElement = null;
|
324 |
+
let history = [];
|
325 |
+
let historyIndex = -1;
|
326 |
+
|
327 |
+
// Initialize with sample data
|
328 |
+
loadJSON(sampleJSON);
|
329 |
+
|
330 |
+
// Event listeners
|
331 |
+
loadSampleBtn.addEventListener('click', () => {
|
332 |
+
loadJSON(sampleJSON);
|
333 |
+
showNotification('Sample JSON loaded successfully!');
|
334 |
+
});
|
335 |
+
|
336 |
+
copyBtn.addEventListener('click', () => {
|
337 |
+
jsonOutput.select();
|
338 |
+
document.execCommand('copy');
|
339 |
+
showNotification('JSON copied to clipboard!');
|
340 |
+
});
|
341 |
+
|
342 |
+
downloadBtn.addEventListener('click', () => {
|
343 |
+
const blob = new Blob([jsonOutput.value], { type: 'application/json' });
|
344 |
+
const url = URL.createObjectURL(blob);
|
345 |
+
const a = document.createElement('a');
|
346 |
+
a.href = url;
|
347 |
+
a.download = 'data.json';
|
348 |
+
document.body.appendChild(a);
|
349 |
+
a.click();
|
350 |
+
document.body.removeChild(a);
|
351 |
+
URL.revokeObjectURL(url);
|
352 |
+
showNotification('JSON file downloaded!');
|
353 |
+
});
|
354 |
+
|
355 |
+
// Load JSON data into the editor
|
356 |
+
function loadJSON(data) {
|
357 |
+
jsonData = JSON.parse(JSON.stringify(data)); // Deep copy
|
358 |
+
renderEditor();
|
359 |
+
updateOutput();
|
360 |
+
saveToHistory();
|
361 |
+
}
|
362 |
+
|
363 |
+
// Render the JSON editor
|
364 |
+
function renderEditor() {
|
365 |
+
jsonEditor.innerHTML = '';
|
366 |
+
renderElement(jsonEditor, jsonData, 0, 'root');
|
367 |
+
}
|
368 |
+
|
369 |
+
// Render a single JSON element
|
370 |
+
function renderElement(container, data, depth, key = null, parentKey = null) {
|
371 |
+
const wrapper = document.createElement('div');
|
372 |
+
wrapper.className = 'json-item relative pl-6';
|
373 |
+
wrapper.style.paddingLeft = `${depth * 20 + 8}px`;
|
374 |
+
wrapper.dataset.key = key;
|
375 |
+
wrapper.dataset.parent = parentKey;
|
376 |
+
wrapper.dataset.depth = depth;
|
377 |
+
|
378 |
+
// Add indent lines
|
379 |
+
if (depth > 0) {
|
380 |
+
const indentLine = document.createElement('div');
|
381 |
+
indentLine.className = 'indent-line';
|
382 |
+
indentLine.style.left = `${depth * 20}px`;
|
383 |
+
wrapper.appendChild(indentLine);
|
384 |
+
}
|
385 |
+
|
386 |
+
// Create element content
|
387 |
+
const content = document.createElement('div');
|
388 |
+
content.className = 'flex items-start py-1';
|
389 |
+
|
390 |
+
// Key
|
391 |
+
if (key !== null && key !== 'root') {
|
392 |
+
const keyElement = document.createElement('span');
|
393 |
+
keyElement.className = 'json-key mr-2';
|
394 |
+
keyElement.textContent = `"${key}": `;
|
395 |
+
content.appendChild(keyElement);
|
396 |
+
}
|
397 |
+
|
398 |
+
// Value or children
|
399 |
+
if (typeof data === 'object' && data !== null) {
|
400 |
+
if (Array.isArray(data)) {
|
401 |
+
// Array
|
402 |
+
const bracket = document.createElement('span');
|
403 |
+
bracket.className = 'json-bracket';
|
404 |
+
bracket.textContent = '[';
|
405 |
+
content.appendChild(bracket);
|
406 |
+
|
407 |
+
wrapper.appendChild(content);
|
408 |
+
container.appendChild(wrapper);
|
409 |
+
|
410 |
+
// Render array items
|
411 |
+
data.forEach((item, index) => {
|
412 |
+
renderElement(container, item, depth + 1, index, key);
|
413 |
+
});
|
414 |
+
|
415 |
+
// Closing bracket
|
416 |
+
const closingWrapper = document.createElement('div');
|
417 |
+
closingWrapper.className = 'json-item relative pl-6';
|
418 |
+
closingWrapper.style.paddingLeft = `${depth * 20 + 8}px`;
|
419 |
+
closingWrapper.dataset.key = 'closing';
|
420 |
+
closingWrapper.dataset.parent = key;
|
421 |
+
closingWrapper.dataset.depth = depth;
|
422 |
+
|
423 |
+
if (depth > 0) {
|
424 |
+
const indentLine = document.createElement('div');
|
425 |
+
indentLine.className = 'indent-line';
|
426 |
+
indentLine.style.left = `${depth * 20}px`;
|
427 |
+
closingWrapper.appendChild(indentLine);
|
428 |
+
}
|
429 |
+
|
430 |
+
const closingContent = document.createElement('div');
|
431 |
+
closingContent.className = 'flex items-start py-1';
|
432 |
+
const closingBracket = document.createElement('span');
|
433 |
+
closingBracket.className = 'json-bracket';
|
434 |
+
closingBracket.textContent = ']';
|
435 |
+
closingContent.appendChild(closingBracket);
|
436 |
+
closingWrapper.appendChild(closingContent);
|
437 |
+
container.appendChild(closingWrapper);
|
438 |
+
} else {
|
439 |
+
// Object
|
440 |
+
const bracket = document.createElement('span');
|
441 |
+
bracket.className = 'json-bracket';
|
442 |
+
bracket.textContent = '{';
|
443 |
+
content.appendChild(bracket);
|
444 |
+
|
445 |
+
wrapper.appendChild(content);
|
446 |
+
container.appendChild(wrapper);
|
447 |
+
|
448 |
+
// Render object properties
|
449 |
+
Object.keys(data).forEach(propKey => {
|
450 |
+
renderElement(container, data[propKey], depth + 1, propKey, key);
|
451 |
+
});
|
452 |
+
|
453 |
+
// Closing bracket
|
454 |
+
const closingWrapper = document.createElement('div');
|
455 |
+
closingWrapper.className = 'json-item relative pl-6';
|
456 |
+
closingWrapper.style.paddingLeft = `${depth * 20 + 8}px`;
|
457 |
+
closingWrapper.dataset.key = 'closing';
|
458 |
+
closingWrapper.dataset.parent = key;
|
459 |
+
closingWrapper.dataset.depth = depth;
|
460 |
+
|
461 |
+
if (depth > 0) {
|
462 |
+
const indentLine = document.createElement('div');
|
463 |
+
indentLine.className = 'indent-line';
|
464 |
+
indentLine.style.left = `${depth * 20}px`;
|
465 |
+
closingWrapper.appendChild(indentLine);
|
466 |
+
}
|
467 |
+
|
468 |
+
const closingContent = document.createElement('div');
|
469 |
+
closingContent.className = 'flex items-start py-1';
|
470 |
+
const closingBracket = document.createElement('span');
|
471 |
+
closingBracket.className = 'json-bracket';
|
472 |
+
closingBracket.textContent = '}';
|
473 |
+
closingContent.appendChild(closingBracket);
|
474 |
+
closingWrapper.appendChild(closingContent);
|
475 |
+
container.appendChild(closingWrapper);
|
476 |
+
}
|
477 |
+
} else {
|
478 |
+
// Primitive value
|
479 |
+
const valueElement = document.createElement('span');
|
480 |
+
valueElement.className = 'json-value';
|
481 |
+
|
482 |
+
if (typeof data === 'string') {
|
483 |
+
valueElement.textContent = `"${data}"`;
|
484 |
+
} else if (typeof data === 'boolean') {
|
485 |
+
valueElement.textContent = data.toString();
|
486 |
+
} else {
|
487 |
+
valueElement.textContent = data;
|
488 |
+
}
|
489 |
+
|
490 |
+
content.appendChild(valueElement);
|
491 |
+
wrapper.appendChild(content);
|
492 |
+
container.appendChild(wrapper);
|
493 |
+
}
|
494 |
+
|
495 |
+
// Add event listeners for editing
|
496 |
+
if (wrapper.dataset.key !== 'closing') {
|
497 |
+
wrapper.addEventListener('dblclick', () => editElement(wrapper));
|
498 |
+
wrapper.addEventListener('click', () => selectElement(wrapper));
|
499 |
+
}
|
500 |
+
}
|
501 |
+
|
502 |
+
// Edit an element
|
503 |
+
function editElement(element) {
|
504 |
+
if (element.dataset.key === 'closing') return;
|
505 |
+
|
506 |
+
const keyElement = element.querySelector('.json-key');
|
507 |
+
const valueElement = element.querySelector('.json-value');
|
508 |
+
|
509 |
+
if (keyElement) {
|
510 |
+
const keyText = keyElement.textContent.replace(/"/g, '').replace(':', '').trim();
|
511 |
+
const input = document.createElement('input');
|
512 |
+
input.type = 'text';
|
513 |
+
input.className = 'editable bg-yellow-100 px-1';
|
514 |
+
input.value = keyText;
|
515 |
+
keyElement.replaceWith(input);
|
516 |
+
input.focus();
|
517 |
+
|
518 |
+
input.addEventListener('blur', () => {
|
519 |
+
const newKey = input.value;
|
520 |
+
updateKey(element, newKey);
|
521 |
+
input.replaceWith(keyElement);
|
522 |
+
keyElement.textContent = `"${newKey}": `;
|
523 |
+
});
|
524 |
+
|
525 |
+
input.addEventListener('keydown', (e) => {
|
526 |
+
if (e.key === 'Enter') {
|
527 |
+
input.blur();
|
528 |
+
}
|
529 |
+
});
|
530 |
+
}
|
531 |
+
|
532 |
+
if (valueElement) {
|
533 |
+
const valueText = valueElement.textContent.replace(/"/g, '');
|
534 |
+
const input = document.createElement('input');
|
535 |
+
input.type = 'text';
|
536 |
+
input.className = 'editable bg-yellow-100 px-1';
|
537 |
+
input.value = valueText;
|
538 |
+
valueElement.replaceWith(input);
|
539 |
+
input.focus();
|
540 |
+
|
541 |
+
input.addEventListener('blur', () => {
|
542 |
+
const newValue = parseValue(input.value);
|
543 |
+
updateValue(element, newValue);
|
544 |
+
input.replaceWith(valueElement);
|
545 |
+
valueElement.textContent = formatValue(newValue);
|
546 |
+
});
|
547 |
+
|
548 |
+
input.addEventListener('keydown', (e) => {
|
549 |
+
if (e.key === 'Enter') {
|
550 |
+
input.blur();
|
551 |
+
}
|
552 |
+
});
|
553 |
+
}
|
554 |
+
}
|
555 |
+
|
556 |
+
// Parse a value from string to appropriate type
|
557 |
+
function parseValue(value) {
|
558 |
+
if (value === 'true') return true;
|
559 |
+
if (value === 'false') return false;
|
560 |
+
if (value === 'null') return null;
|
561 |
+
if (!isNaN(value) && value.trim() !== '') return Number(value);
|
562 |
+
return value;
|
563 |
+
}
|
564 |
+
|
565 |
+
// Format a value for display
|
566 |
+
function formatValue(value) {
|
567 |
+
if (typeof value === 'string') return `"${value}"`;
|
568 |
+
if (typeof value === 'boolean') return value.toString();
|
569 |
+
if (value === null) return 'null';
|
570 |
+
return value;
|
571 |
+
}
|
572 |
+
|
573 |
+
// Update a key
|
574 |
+
function updateKey(element, newKey) {
|
575 |
+
const parentKey = element.dataset.parent;
|
576 |
+
const oldKey = element.dataset.key;
|
577 |
+
|
578 |
+
if (parentKey === 'root') {
|
579 |
+
const newData = {};
|
580 |
+
Object.keys(jsonData).forEach(key => {
|
581 |
+
if (key === oldKey) {
|
582 |
+
newData[newKey] = jsonData[key];
|
583 |
+
} else if (key !== newKey) {
|
584 |
+
newData[key] = jsonData[key];
|
585 |
+
}
|
586 |
+
});
|
587 |
+
jsonData = newData;
|
588 |
+
} else {
|
589 |
+
// Find parent in nested structure
|
590 |
+
const parent = findElementByKey(jsonData, parentKey);
|
591 |
+
if (parent && typeof parent === 'object') {
|
592 |
+
const newData = {};
|
593 |
+
Object.keys(parent).forEach(key => {
|
594 |
+
if (key === oldKey) {
|
595 |
+
newData[newKey] = parent[key];
|
596 |
+
} else if (key !== newKey) {
|
597 |
+
newData[key] = parent[key];
|
598 |
+
}
|
599 |
+
});
|
600 |
+
Object.keys(parent).forEach(key => delete parent[key]);
|
601 |
+
Object.assign(parent, newData);
|
602 |
+
}
|
603 |
+
}
|
604 |
+
|
605 |
+
element.dataset.key = newKey;
|
606 |
+
updateOutput();
|
607 |
+
saveToHistory();
|
608 |
+
}
|
609 |
+
|
610 |
+
// Update a value
|
611 |
+
function updateValue(element, newValue) {
|
612 |
+
const key = element.dataset.key;
|
613 |
+
const parentKey = element.dataset.parent;
|
614 |
+
|
615 |
+
if (parentKey === 'root') {
|
616 |
+
jsonData[key] = newValue;
|
617 |
+
} else {
|
618 |
+
const parent = findElementByKey(jsonData, parentKey);
|
619 |
+
if (parent && typeof parent === 'object') {
|
620 |
+
parent[key] = newValue;
|
621 |
+
}
|
622 |
+
}
|
623 |
+
|
624 |
+
updateOutput();
|
625 |
+
saveToHistory();
|
626 |
+
}
|
627 |
+
|
628 |
+
// Find an element by key in nested structure
|
629 |
+
function findElementByKey(obj, key) {
|
630 |
+
if (obj[key] !== undefined) return obj;
|
631 |
+
|
632 |
+
for (let prop in obj) {
|
633 |
+
if (typeof obj[prop] === 'object' && obj[prop] !== null) {
|
634 |
+
const result = findElementByKey(obj[prop], key);
|
635 |
+
if (result) return result;
|
636 |
+
}
|
637 |
+
}
|
638 |
+
|
639 |
+
return null;
|
640 |
+
}
|
641 |
+
|
642 |
+
// Select an element
|
643 |
+
function selectElement(element) {
|
644 |
+
if (selectedElement) {
|
645 |
+
selectedElement.classList.remove('selected');
|
646 |
+
}
|
647 |
+
|
648 |
+
element.classList.add('selected');
|
649 |
+
selectedElement = element;
|
650 |
+
}
|
651 |
+
|
652 |
+
// Update JSON output
|
653 |
+
function updateOutput() {
|
654 |
+
try {
|
655 |
+
jsonOutput.value = JSON.stringify(jsonData, null, 2);
|
656 |
+
jsonOutput.classList.remove('error-highlight');
|
657 |
+
} catch (e) {
|
658 |
+
jsonOutput.value = 'Invalid JSON structure';
|
659 |
+
jsonOutput.classList.add('error-highlight');
|
660 |
+
}
|
661 |
+
}
|
662 |
+
|
663 |
+
// Show notification
|
664 |
+
function showNotification(message) {
|
665 |
+
const notificationContent = notification.querySelector('p');
|
666 |
+
notificationContent.textContent = message;
|
667 |
+
notification.classList.add('show');
|
668 |
+
|
669 |
+
setTimeout(() => {
|
670 |
+
notification.classList.remove('show');
|
671 |
+
}, 3000);
|
672 |
+
}
|
673 |
+
|
674 |
+
// Save to history for undo/redo
|
675 |
+
function saveToHistory() {
|
676 |
+
// Remove future history if we're not at the end
|
677 |
+
if (historyIndex < history.length - 1) {
|
678 |
+
history = history.slice(0, historyIndex + 1);
|
679 |
+
}
|
680 |
+
|
681 |
+
history.push(JSON.parse(JSON.stringify(jsonData)));
|
682 |
+
historyIndex = history.length - 1;
|
683 |
+
}
|
684 |
+
|
685 |
+
// Undo action
|
686 |
+
function undo() {
|
687 |
+
if (historyIndex > 0) {
|
688 |
+
historyIndex--;
|
689 |
+
jsonData = JSON.parse(JSON.stringify(history[historyIndex]));
|
690 |
+
renderEditor();
|
691 |
+
updateOutput();
|
692 |
+
showNotification('Undo successful');
|
693 |
+
}
|
694 |
+
}
|
695 |
+
|
696 |
+
// Redo action
|
697 |
+
function redo() {
|
698 |
+
if (historyIndex < history.length - 1) {
|
699 |
+
historyIndex++;
|
700 |
+
jsonData = JSON.parse(JSON.stringify(history[historyIndex]));
|
701 |
+
renderEditor();
|
702 |
+
updateOutput();
|
703 |
+
showNotification('Redo successful');
|
704 |
+
}
|
705 |
+
}
|
706 |
+
|
707 |
+
// Event listeners for undo/redo
|
708 |
+
document.getElementById('undoBtn').addEventListener('click', undo);
|
709 |
+
document.getElementById('redoBtn').addEventListener('click', redo);
|
710 |
+
|
711 |
+
// Format JSON
|
712 |
+
document.getElementById('formatBtn').addEventListener('click', () => {
|
713 |
+
updateOutput();
|
714 |
+
showNotification('JSON formatted');
|
715 |
+
});
|
716 |
+
|
717 |
+
// Validate JSON
|
718 |
+
document.getElementById('validateBtn').addEventListener('click', () => {
|
719 |
+
try {
|
720 |
+
JSON.parse(jsonOutput.value);
|
721 |
+
showNotification('JSON is valid!');
|
722 |
+
} catch (e) {
|
723 |
+
showNotification('Invalid JSON: ' + e.message);
|
724 |
+
}
|
725 |
+
});
|
726 |
+
});
|
727 |
+
</script>
|
728 |
+
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=RobinsAIWorld/jsonic-v2" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
|
729 |
+
</html>
|