jsonic-v2 / index.html
RobinsAIWorld's picture
I tried to paste in clipboard contents of mcp_config.json but it again opened the deepsite editor pane - Follow Up Deployment
f80e511 verified
raw
history blame
42.7 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Visual JSON Editor</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#3b82f6',
secondary: '#1e40af',
dark: '#0f172a',
light: '#f8fafc'
}
}
}
}
</script>
<style>
.json-editor {
min-height: 500px;
max-height: 70vh;
overflow-y: auto;
background-color: #1e293b;
background-image: radial-gradient(#334155 1px, transparent 1px);
background-size: 20px 20px;
padding: 20px;
border-radius: 8px;
}
.json-item {
transition: all 0.2s ease;
margin: 5px 0;
border-radius: 6px;
background-color: #334155;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
position: relative;
display: block;
}
.json-item-content {
padding: 8px 12px;
margin-left: 0;
display: inline-block;
min-width: 200px;
border-left: 3px solid transparent;
}
.json-item:hover .json-item-content {
background-color: #475569;
border-left: 3px solid #60a5fa;
}
.json-item.selected .json-item-content {
background-color: #475569;
border-left: 3px solid #60a5fa;
box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.3);
}
.json-item.editing .json-item-content {
background-color: #64748b;
border-left: 3px solid #fbbf24;
box-shadow: 0 0 0 3px rgba(251, 191, 36, 0.5);
}
.json-item:hover {
background-color: #475569;
border-left: 3px solid #60a5fa;
}
.json-item.selected {
background-color: #475569;
border-left: 3px solid #60a5fa;
box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.3);
}
.json-item.editing {
background-color: #64748b;
border-left: 3px solid #fbbf24;
box-shadow: 0 0 0 3px rgba(251, 191, 36, 0.5);
}
.json-item.dragging {
opacity: 0.5;
background-color: #475569;
}
.json-item.drag-over {
border-top: 2px dashed #60a5fa;
}
.json-key {
font-weight: 600;
color: #93c5fd;
margin-right: 8px;
}
.json-value {
color: #6ee7b7;
}
.json-bracket {
color: #94a3b8;
}
.btn {
transition: all 0.2s ease;
}
.btn:hover {
transform: translateY(-2px);
}
.indent-line {
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 1px;
background-color: #475569;
}
.cursor-pointer {
cursor: pointer;
}
.editable:focus {
outline: 2px solid #60a5fa;
border-radius: 4px;
}
.editable {
min-width: 20px;
display: inline-block;
background-color: rgba(96, 165, 250, 0.1);
padding: 2px 4px;
border-radius: 4px;
}
.toolbar-btn {
transition: all 0.2s;
}
.toolbar-btn:hover {
background-color: #475569;
}
.toolbar-btn.active {
background-color: #60a5fa;
color: white;
}
.notification {
transform: translateX(100%);
transition: transform 0.3s ease;
}
.notification.show {
transform: translateX(0);
}
.error-highlight {
background-color: #fee2e2;
border-left: 3px solid #ef4444;
}
.menu-bar {
background-color: #334155;
padding: 8px 16px;
border-radius: 8px;
margin-bottom: 16px;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
.menu-item {
color: #cbd5e1;
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
margin-right: 4px;
}
.menu-item:hover {
background-color: #475569;
}
.menu-item.active {
background-color: #60a5fa;
color: white;
}
.dropdown {
position: relative;
display: inline-block;
}
.dropdown-content {
display: none;
position: absolute;
background-color: #334155;
min-width: 160px;
box-shadow: 0px 8px 16px rgba(0,0,0,0.2);
z-index: 1;
border-radius: 4px;
overflow: hidden;
}
.dropdown-content a {
color: #cbd5e1;
padding: 12px 16px;
text-decoration: none;
display: block;
}
.dropdown-content a:hover {
background-color: #475569;
}
.dropdown:hover .dropdown-content {
display: block;
}
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
}
.modal-content {
background-color: #334155;
margin: 10% auto;
padding: 20px;
border-radius: 8px;
width: 80%;
max-width: 600px;
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
color: #f1f5f9;
}
.close {
color: #94a3b8;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
.close:hover {
color: #f1f5f9;
}
.boolean-select {
background-color: #475569;
color: #f1f5f9;
border: 1px solid #64748b;
border-radius: 4px;
padding: 2px 4px;
}
</style>
</head>
<body class="bg-slate-900 min-h-screen p-4 md:p-8">
<div class="max-w-6xl mx-auto">
<header class="mb-8 text-center">
<h1 class="text-3xl md:text-4xl font-bold text-slate-100 mb-2">Visual JSON Editor</h1>
<p class="text-slate-300">Edit JSON with drag-and-drop and intuitive keyboard navigation</p>
</header>
<!-- Menu Bar -->
<div class="menu-bar flex flex-wrap">
<div class="dropdown">
<div class="menu-item">File</div>
<div class="dropdown-content">
<a href="#" id="newBtn"><i class="fas fa-plus mr-2"></i>New</a>
<a href="#" id="openBtn"><i class="fas fa-folder-open mr-2"></i>Open</a>
<a href="#" id="saveBtn"><i class="fas fa-save mr-2"></i>Save</a>
</div>
</div>
<div class="dropdown">
<div class="menu-item">Edit</div>
<div class="dropdown-content">
<a href="#" id="undoBtn"><i class="fas fa-undo mr-2"></i>Undo</a>
<a href="#" id="redoBtn"><i class="fas fa-redo mr-2"></i>Redo</a>
<a href="#" id="copyBtnMenu"><i class="fas fa-copy mr-2"></i>Copy</a>
<a href="#" id="cutBtnMenu"><i class="fas fa-cut mr-2"></i>Cut</a>
<a href="#" id="pasteBtnMenu"><i class="fas fa-paste mr-2"></i>Paste</a>
<a href="#" id="preferencesBtn"><i class="fas fa-cog mr-2"></i>Preferences</a>
</div>
</div>
<div class="dropdown">
<div class="menu-item">Tools</div>
<div class="dropdown-content">
<a href="#" id="formatBtn"><i class="fas fa-indent mr-2"></i>Format</a>
<a href="#" id="validateBtn"><i class="fas fa-check-circle mr-2"></i>Validate</a>
</div>
</div>
<div class="dropdown">
<div class="menu-item">View</div>
<div class="dropdown-content">
<a href="#" id="viewModeBtn"><i class="fas fa-eye mr-2"></i>View Mode</a>
<a href="#" id="editModeBtn"><i class="fas fa-edit mr-2"></i>Edit Mode</a>
</div>
</div>
<div class="dropdown">
<div class="menu-item">Help</div>
<div class="dropdown-content">
<a href="#" id="instructionsBtn"><i class="fas fa-info-circle mr-2"></i>Instructions</a>
<a href="#" id="sampleBtn"><i class="fas fa-code mr-2"></i>Sample JSON</a>
</div>
</div>
</div>
<div class="bg-slate-800 rounded-xl shadow-lg overflow-hidden mb-8">
<div class="p-4 bg-slate-700 border-b flex flex-wrap items-center justify-between gap-2">
<div class="flex flex-wrap gap-2">
<button id="newBtn2" class="btn bg-primary hover:bg-secondary text-white px-4 py-2 rounded-lg flex items-center gap-2">
<i class="fas fa-plus"></i> New
</button>
<button id="openBtn2" class="btn bg-slate-600 hover:bg-slate-500 px-4 py-2 rounded-lg flex items-center gap-2">
<i class="fas fa-folder-open"></i> Open
</button>
<button id="saveBtn2" class="btn bg-slate-600 hover:bg-slate-500 px-4 py-2 rounded-lg flex items-center gap-2">
<i class="fas fa-save"></i> Save
</button>
</div>
<div class="flex flex-wrap gap-2">
<button id="undoBtn2" class="btn bg-slate-600 hover:bg-slate-500 px-3 py-2 rounded-lg">
<i class="fas fa-undo"></i>
</button>
<button id="redoBtn2" class="btn bg-slate-600 hover:bg-slate-500 px-3 py-2 rounded-lg">
<i class="fas fa-redo"></i>
</button>
<button id="formatBtn2" class="btn bg-slate-600 hover:bg-slate-500 px-3 py-2 rounded-lg">
<i class="fas fa-indent"></i>
</button>
<button id="validateBtn2" class="btn bg-slate-600 hover:bg-slate-500 px-3 py-2 rounded-lg">
<i class="fas fa-check-circle"></i>
</button>
</div>
</div>
<div class="p-4 bg-gray-50 border-b flex flex-wrap gap-2">
<div class="flex items-center gap-2 bg-white px-3 py-1 rounded-lg shadow-sm">
<span class="text-gray-600">Mode:</span>
<div class="flex gap-1">
<button id="viewModeBtn" class="toolbar-btn px-3 py-1 rounded-md">View</button>
<button id="editModeBtn" class="toolbar-btn px-3 py-1 rounded-md active">Edit</button>
</div>
</div>
<div class="flex items-center gap-2 bg-white px-3 py-1 rounded-lg shadow-sm">
<span class="text-gray-600">Indent:</span>
<div class="flex gap-1">
<button id="indentBtn" class="toolbar-btn px-3 py-1 rounded-md">2</button>
<button id="indentBtn4" class="toolbar-btn px-3 py-1 rounded-md">4</button>
</div>
</div>
</div>
<div class="p-4 flex flex-col md:flex-row gap-4">
<div class="w-full">
<div class="bg-slate-700 rounded-lg p-4 mb-4">
<h2 class="text-lg font-semibold text-slate-200 mb-2">JSON Editor</h2>
<div id="jsonEditor" class="json-editor border rounded-lg p-4 font-mono min-h-[400px] relative">
<!-- JSON content will be rendered here -->
</div>
</div>
<div class="bg-gray-50 rounded-lg p-4">
<h2 class="text-lg font-semibold text-gray-700 mb-2">JSON Output</h2>
<textarea id="jsonOutput" class="w-full h-40 font-mono text-sm p-3 border rounded-lg bg-white" readonly></textarea>
<div class="mt-2 flex justify-between">
<button id="copyBtn" class="btn bg-gray-200 hover:bg-gray-300 px-4 py-2 rounded-lg">
<i class="fas fa-copy"></i> Copy JSON
</button>
<button id="downloadBtn" class="btn bg-primary hover:bg-secondary text-white px-4 py-2 rounded-lg">
<i class="fas fa-download"></i> Download
</button>
</div>
</div>
</div>
<div class="w-full md:w-1/2">
<div class="bg-gray-50 rounded-lg p-4 h-full">
<h2 class="text-lg font-semibold text-gray-700 mb-2">Preview</h2>
<div class="bg-white border rounded-lg p-4 h-[400px] overflow-y-auto">
<p class="text-gray-600">JSON structure will be displayed here in view mode.</p>
</div>
</div>
</div>
</div>
</div>
<div class="bg-white rounded-xl shadow-lg p-6">
<h2 class="text-xl font-bold text-gray-800 mb-4">How It Works</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="bg-blue-50 p-4 rounded-lg border border-blue-200">
<div class="text-blue-500 text-2xl mb-2">
<i class="fas fa-magic"></i>
</div>
<h3 class="font-bold text-lg mb-2">Visual Editing</h3>
<p class="text-gray-700">Easily edit JSON without worrying about brackets or indentation. Each element is visually represented for intuitive editing.</p>
</div>
<div class="bg-green-50 p-4 rounded-lg border border-green-200">
<div class="text-green-500 text-2xl mb-2">
<i class="fas fa-sync-alt"></i>
</div>
<h3 class="font-bold text-lg mb-2">Real-time Sync</h3>
<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>
</div>
<div class="bg-purple-50 p-4 rounded-lg border border-purple-200">
<div class="text-purple-500 text-2xl mb-2">
<i class="fas fa-robot"></i>
</div>
<h3 class="font-bold text-lg mb-2">Auto-Repair</h3>
<p class="text-gray-700">Paste malformed JSON and our editor will attempt to automatically repair common syntax errors and structural issues.</p>
</div>
</div>
</div>
<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">
<div class="flex items-start">
<i class="fas fa-check-circle text-green-500 text-xl mt-0.5 mr-3"></i>
<div>
<h4 class="font-bold text-gray-800">Success!</h4>
<p class="text-gray-600 mt-1">Your JSON has been updated successfully.</p>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Sample JSON data
const sampleJSON = {
"name": "John Doe",
"age": 30,
"isStudent": false,
"address": {
"street": "123 Main St",
"city": "Anytown",
"zipcode": "12345"
},
"hobbies": [
"reading",
"swimming",
"coding"
],
"contact": {
"email": "[email protected]",
"phone": "555-1234"
}
};
// DOM elements
const jsonEditor = document.getElementById('jsonEditor');
const jsonOutput = document.getElementById('jsonOutput');
const openBtn = document.getElementById('openBtn');
const openBtn2 = document.getElementById('openBtn2');
const notification = document.getElementById('notification');
const copyBtn = document.getElementById('copyBtn');
const downloadBtn = document.getElementById('downloadBtn');
// Current state
let jsonData = {};
let selectedElement = null;
let history = [];
let historyIndex = -1;
// Initialize with sample data
loadJSON(sampleJSON);
// Event listeners
// Create hidden file input for opening files
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'application/json,.json';
fileInput.style.display = 'none';
document.body.appendChild(fileInput);
// Open file functionality
function openFile() {
fileInput.click();
}
fileInput.addEventListener('change', (event) => {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = JSON.parse(e.target.result);
loadJSON(data);
showNotification('File loaded successfully!');
} catch (error) {
showNotification('Error loading file: Invalid JSON');
}
};
reader.readAsText(file);
}
});
openBtn.addEventListener('click', openFile);
openBtn2.addEventListener('click', openFile);
copyBtn.addEventListener('click', () => {
jsonOutput.select();
document.execCommand('copy');
showNotification('JSON copied to clipboard!');
});
downloadBtn.addEventListener('click', () => {
const blob = new Blob([jsonOutput.value], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'data.json';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showNotification('JSON file downloaded!');
});
// Load JSON data into the editor
function loadJSON(data) {
jsonData = JSON.parse(JSON.stringify(data)); // Deep copy
renderEditor();
updateOutput();
saveToHistory();
}
// Render the JSON editor
function renderEditor() {
jsonEditor.innerHTML = '';
renderElement(jsonEditor, jsonData, 0, 'root');
}
// Render a single JSON element
function renderElement(container, data, depth, key = null, parentKey = null) {
const wrapper = document.createElement('div');
wrapper.className = 'json-item relative';
wrapper.dataset.key = key;
wrapper.dataset.parent = parentKey;
wrapper.dataset.depth = depth;
// Add indent lines
if (depth > 0) {
const indentLine = document.createElement('div');
indentLine.className = 'indent-line';
indentLine.style.left = `${depth * 20}px`;
wrapper.appendChild(indentLine);
}
// Create element content
const content = document.createElement('div');
content.className = 'json-item-content flex items-start py-1';
content.style.marginLeft = `${depth * 20 + 8}px`;
// Key
if (key !== null && key !== 'root') {
const keyElement = document.createElement('span');
keyElement.className = 'json-key mr-2';
keyElement.textContent = `"${key}": `;
content.appendChild(keyElement);
}
// Value or children
if (typeof data === 'object' && data !== null) {
if (Array.isArray(data)) {
// Array
const bracket = document.createElement('span');
bracket.className = 'json-bracket';
bracket.textContent = '[';
content.appendChild(bracket);
wrapper.appendChild(content);
container.appendChild(wrapper);
// Render array items
data.forEach((item, index) => {
renderElement(container, item, depth + 1, index, key);
});
// Closing bracket
const closingWrapper = document.createElement('div');
closingWrapper.className = 'json-item relative';
closingWrapper.dataset.key = 'closing';
closingWrapper.dataset.parent = key;
closingWrapper.dataset.depth = depth;
const closingContent = document.createElement('div');
closingContent.className = 'json-item-content flex items-start py-1';
closingContent.style.marginLeft = `${depth * 20 + 8}px`;
if (depth > 0) {
const indentLine = document.createElement('div');
indentLine.className = 'indent-line';
indentLine.style.left = `${depth * 20}px`;
closingWrapper.appendChild(indentLine);
}
const closingContent = document.createElement('div');
closingContent.className = 'flex items-start py-1';
const closingBracket = document.createElement('span');
closingBracket.className = 'json-bracket';
closingBracket.textContent = ']';
closingContent.appendChild(closingBracket);
closingWrapper.appendChild(closingContent);
container.appendChild(closingWrapper);
} else {
// Object
const bracket = document.createElement('span');
bracket.className = 'json-bracket';
bracket.textContent = '{';
content.appendChild(bracket);
wrapper.appendChild(content);
container.appendChild(wrapper);
// Render object properties
Object.keys(data).forEach(propKey => {
renderElement(container, data[propKey], depth + 1, propKey, key);
});
// Closing bracket
const closingWrapper = document.createElement('div');
closingWrapper.className = 'json-item relative';
closingWrapper.dataset.key = 'closing';
closingWrapper.dataset.parent = key;
closingWrapper.dataset.depth = depth;
const closingContent = document.createElement('div');
closingContent.className = 'json-item-content flex items-start py-1';
closingContent.style.marginLeft = `${depth * 20 + 8}px`;
if (depth > 0) {
const indentLine = document.createElement('div');
indentLine.className = 'indent-line';
indentLine.style.left = `${depth * 20}px`;
closingWrapper.appendChild(indentLine);
}
const closingContent = document.createElement('div');
closingContent.className = 'flex items-start py-1';
const closingBracket = document.createElement('span');
closingBracket.className = 'json-bracket';
closingBracket.textContent = '}';
closingContent.appendChild(closingBracket);
closingWrapper.appendChild(closingContent);
container.appendChild(closingWrapper);
}
} else {
// Primitive value
const valueElement = document.createElement('span');
valueElement.className = 'json-value';
if (typeof data === 'string') {
valueElement.textContent = `"${data}"`;
} else if (typeof data === 'boolean') {
valueElement.textContent = data.toString();
} else {
valueElement.textContent = data;
}
content.appendChild(valueElement);
wrapper.appendChild(content);
container.appendChild(wrapper);
}
// Add event listeners for editing
if (wrapper.dataset.key !== 'closing') {
wrapper.addEventListener('dblclick', () => editElement(wrapper));
wrapper.addEventListener('click', () => selectElement(wrapper));
}
}
// Edit an element
function editElement(element) {
if (element.dataset.key === 'closing') return;
const keyElement = element.querySelector('.json-key');
const valueElement = element.querySelector('.json-value');
if (keyElement) {
const keyText = keyElement.textContent.replace(/"/g, '').replace(':', '').trim();
const input = document.createElement('input');
input.type = 'text';
input.className = 'editable bg-yellow-100 px-1';
input.value = keyText;
keyElement.replaceWith(input);
input.focus();
input.addEventListener('blur', () => {
const newKey = input.value;
updateKey(element, newKey);
input.replaceWith(keyElement);
keyElement.textContent = `"${newKey}": `;
});
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
input.blur();
}
});
}
if (valueElement) {
const valueText = valueElement.textContent.replace(/"/g, '');
const input = document.createElement('input');
input.type = 'text';
input.className = 'editable bg-yellow-100 px-1';
input.value = valueText;
valueElement.replaceWith(input);
input.focus();
input.addEventListener('blur', () => {
const newValue = parseValue(input.value);
updateValue(element, newValue);
input.replaceWith(valueElement);
valueElement.textContent = formatValue(newValue);
});
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
input.blur();
}
});
}
}
// Parse a value from string to appropriate type
function parseValue(value) {
if (value === 'true') return true;
if (value === 'false') return false;
if (value === 'null') return null;
if (!isNaN(value) && value.trim() !== '') return Number(value);
return value;
}
// Format a value for display
function formatValue(value) {
if (typeof value === 'string') return `"${value}"`;
if (typeof value === 'boolean') return value.toString();
if (value === null) return 'null';
return value;
}
// Update a key
function updateKey(element, newKey) {
const parentKey = element.dataset.parent;
const oldKey = element.dataset.key;
if (parentKey === 'root') {
const newData = {};
Object.keys(jsonData).forEach(key => {
if (key === oldKey) {
newData[newKey] = jsonData[key];
} else if (key !== newKey) {
newData[key] = jsonData[key];
}
});
jsonData = newData;
} else {
// Find parent in nested structure
const parent = findElementByKey(jsonData, parentKey);
if (parent && typeof parent === 'object') {
const newData = {};
Object.keys(parent).forEach(key => {
if (key === oldKey) {
newData[newKey] = parent[key];
} else if (key !== newKey) {
newData[key] = parent[key];
}
});
Object.keys(parent).forEach(key => delete parent[key]);
Object.assign(parent, newData);
}
}
element.dataset.key = newKey;
updateOutput();
saveToHistory();
}
// Update a value
function updateValue(element, newValue) {
const key = element.dataset.key;
const parentKey = element.dataset.parent;
if (parentKey === 'root') {
jsonData[key] = newValue;
} else {
const parent = findElementByKey(jsonData, parentKey);
if (parent && typeof parent === 'object') {
parent[key] = newValue;
}
}
updateOutput();
saveToHistory();
}
// Find an element by key in nested structure
function findElementByKey(obj, key) {
if (obj[key] !== undefined) return obj;
for (let prop in obj) {
if (typeof obj[prop] === 'object' && obj[prop] !== null) {
const result = findElementByKey(obj[prop], key);
if (result) return result;
}
}
return null;
}
// Select an element
function selectElement(element) {
if (selectedElement) {
selectedElement.classList.remove('selected');
}
element.classList.add('selected');
selectedElement = element;
}
// Update JSON output
function updateOutput() {
try {
jsonOutput.value = JSON.stringify(jsonData, null, 2);
jsonOutput.classList.remove('error-highlight');
} catch (e) {
jsonOutput.value = 'Invalid JSON structure';
jsonOutput.classList.add('error-highlight');
}
}
// Show notification
function showNotification(message) {
const notificationContent = notification.querySelector('p');
notificationContent.textContent = message;
notification.classList.add('show');
setTimeout(() => {
notification.classList.remove('show');
}, 3000);
}
// Save to history for undo/redo
function saveToHistory() {
// Remove future history if we're not at the end
if (historyIndex < history.length - 1) {
history = history.slice(0, historyIndex + 1);
}
history.push(JSON.parse(JSON.stringify(jsonData)));
historyIndex = history.length - 1;
}
// Undo action
function undo() {
if (historyIndex > 0) {
historyIndex--;
jsonData = JSON.parse(JSON.stringify(history[historyIndex]));
renderEditor();
updateOutput();
showNotification('Undo successful');
}
}
// Redo action
function redo() {
if (historyIndex < history.length - 1) {
historyIndex++;
jsonData = JSON.parse(JSON.stringify(history[historyIndex]));
renderEditor();
updateOutput();
showNotification('Redo successful');
}
}
// Event listeners for undo/redo
document.getElementById('undoBtn').addEventListener('click', undo);
document.getElementById('redoBtn').addEventListener('click', redo);
// Event listeners for copy, cut, paste
document.getElementById('copyBtnMenu').addEventListener('click', () => {
if (selectedElement) {
const range = document.createRange();
range.selectNode(selectedElement);
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
document.execCommand('copy');
window.getSelection().removeAllRanges();
showNotification('Copied to clipboard');
}
});
document.getElementById('cutBtnMenu').addEventListener('click', () => {
if (selectedElement) {
const range = document.createRange();
range.selectNode(selectedElement);
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
document.execCommand('cut');
window.getSelection().removeAllRanges();
showNotification('Cut to clipboard');
}
});
document.getElementById('pasteBtnMenu').addEventListener('click', () => {
navigator.clipboard.readText().then(text => {
// Try to parse as JSON and load if valid
try {
const data = JSON.parse(text);
loadJSON(data);
showNotification('JSON pasted successfully');
} catch (e) {
// If not valid JSON, just show notification
showNotification('Clipboard content is not valid JSON');
}
}).catch(err => {
showNotification('Failed to read clipboard contents');
});
});
// Enable pasting in JSON output pane
jsonOutput.addEventListener('paste', (e) => {
e.preventDefault();
navigator.clipboard.readText().then(text => {
try {
const data = JSON.parse(text);
loadJSON(data);
showNotification('JSON pasted successfully');
} catch (parseError) {
// If not valid JSON, paste as plain text
const start = jsonOutput.selectionStart;
const end = jsonOutput.selectionEnd;
const currentValue = jsonOutput.value;
jsonOutput.value = currentValue.substring(0, start) + text + currentValue.substring(end);
showNotification('Pasted as plain text');
}
}).catch(err => {
// Fallback to clipboardData for older browsers
const text = (e.originalEvent || e).clipboardData.getData('text/plain');
try {
const data = JSON.parse(text);
loadJSON(data);
showNotification('JSON pasted successfully');
} catch (parseError) {
const start = jsonOutput.selectionStart;
const end = jsonOutput.selectionEnd;
const currentValue = jsonOutput.value;
jsonOutput.value = currentValue.substring(0, start) + text + currentValue.substring(end);
showNotification('Pasted as plain text');
}
});
});
// Enable pasting in JSON editor pane
jsonEditor.addEventListener('paste', (e) => {
e.preventDefault();
navigator.clipboard.readText().then(text => {
try {
const data = JSON.parse(text);
loadJSON(data);
showNotification('JSON pasted successfully');
} catch (parseError) {
showNotification('Clipboard content is not valid JSON');
}
}).catch(err => {
// Fallback to clipboardData for older browsers
const text = (e.originalEvent || e).clipboardData.getData('text/plain');
try {
const data = JSON.parse(text);
loadJSON(data);
showNotification('JSON pasted successfully');
} catch (parseError) {
showNotification('Clipboard content is not valid JSON');
}
});
});
// Format JSON
document.getElementById('formatBtn').addEventListener('click', () => {
updateOutput();
showNotification('JSON formatted');
});
// Validate JSON
document.getElementById('validateBtn').addEventListener('click', () => {
try {
JSON.parse(jsonOutput.value);
showNotification('JSON is valid!');
} catch (e) {
showNotification('Invalid JSON: ' + e.message);
}
});
});
</script>
<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>
</html>