diff --git a/json-workbench.html b/json-workbench.html index 74580ba..0da4bf1 100644 --- a/json-workbench.html +++ b/json-workbench.html @@ -245,75 +245,126 @@ body.dark-mode .jse-table-mode, body.dark-mode .jse-welcome, body.dark-mode .jse-theme-dark { - --jse-bg-color: #16171a !important; - --jse-main-border: 1px solid #2a2b30 !important; + /* Grundfarben. --jse-background-color ist die wichtigste hier: Sie ist + der Hintergrund, auf den viele andere Variablen zurückfallen, wenn sie + selbst nicht gesetzt sind (u. a. die Box, die beim Umbenennen eines + Feldes erscheint - ohne diese Variable blieb sie hell). */ + --jse-theme: dark !important; + --jse-background-color: #16171a !important; + /* Text/Tree/Table-Leiste an die App-eigene .top-bar angleichen (dunkel, + kein Akzentorange - siehe Kommentar weiter oben bei .editor-wrapper) */ --jse-theme-color: #1c1d21 !important; --jse-theme-color-highlight: #24252b !important; - --jse-menu-color: #e7e8ea !important; --jse-text-color: #e7e8ea !important; - --jse-text-color-min: #8b8d93 !important; - --jse-key-color: #6cb6ff !important; - --jse-value-color-string: #9fd6a0 !important; - --jse-value-color-number: #f7b267 !important; - --jse-value-color-boolean: #f47067 !important; - --jse-value-color-null: #8b8d93 !important; - --jse-hover-background-color: #22232a !important; - --jse-selection-background-color: #2a3340 !important; - --jse-active-line-background-color: rgba(255,255,255,0.04) !important; + --jse-text-readonly: #8b8d93 !important; + --jse-text-color-inverse: #e7e8ea !important; + --jse-error-color: #e5484d !important; + --jse-warning-color: #f7b267 !important; + --jse-info-color: #6cb6ff !important; + + --jse-main-border: 1px solid #2a2b30 !important; + --jse-menu-color: #e7e8ea !important; + --jse-modal-background: #16171a !important; + --jse-overlay-background: rgba(0,0,0,0.6) !important; + --jse-modal-code-background: rgba(255,255,255,0.05) !important; + + --jse-tooltip-color: #e7e8ea !important; + --jse-tooltip-background: #1c1d21 !important; + --jse-panel-background: #1c1d21 !important; - --jse-panel-background-hover: #24252b !important; --jse-panel-color: #e7e8ea !important; --jse-panel-color-readonly: #8b8d93 !important; --jse-panel-border: 1px solid #2a2b30 !important; --jse-panel-button-color: #c7c8cc !important; + --panel-button-color-highlight: #ffffff !important; --jse-panel-button-color-highlight: #ffffff !important; --jse-panel-button-background: transparent !important; --jse-panel-button-background-highlight: rgba(255,255,255,0.08) !important; + --jse-navigation-bar-background: #1c1d21 !important; --jse-navigation-bar-background-highlight: #24252b !important; --jse-navigation-bar-dropdown-color: #e7e8ea !important; + --jse-context-menu-background: #1c1d21 !important; --jse-context-menu-background-highlight: #24252b !important; --jse-context-menu-color: #e7e8ea !important; + --jse-context-menu-color-disabled: #55565c !important; + --jse-context-menu-separator-color: #2a2b30 !important; + --jse-context-menu-pointer-hover-background: #33343b !important; --jse-context-menu-pointer-background: #1c1d21 !important; --jse-context-menu-pointer-background-highlight: #26272d !important; --jse-context-menu-pointer-color: #e7e8ea !important; - --jse-context-menu-separator-color: #2a2b30 !important; - --jse-context-menu-hover-background-color: #26272d !important; - --jse-modal-background: #16171a !important; - --jse-modal-overlay-background: rgba(0,0,0,0.6) !important; - --jse-input-background: #101114 !important; - --jse-button-background: #26272d !important; - --jse-button-background-highlight: #33343b !important; - --jse-button-color: #e7e8ea !important; - --jse-tooltip-background: #1c1d21 !important; - --jse-tooltip-color: #e7e8ea !important; - --jse-tooltip-error-background: #3a1414 !important; - --jse-tooltip-error-color: #ff9a9a !important; - --jse-search-match-background: rgba(108,182,255,0.22) !important; - --jse-search-match-color: #e7e8ea !important; - --jse-search-match-outline: 1px solid #6cb6ff !important; - --jse-search-match-active-background: #3b78b0 !important; - --jse-search-match-active-color: #ffffff !important; - --jse-search-match-active-outline: 1px solid #8fcaff !important; - --jse-tag-background: #24252b !important; - --jse-tag-color: #e7e8ea !important; + + /* Schlüssel/Werte im Baum */ + --jse-key-color: #6cb6ff !important; + --jse-value-color: #e7e8ea !important; + --jse-value-color-string: #9fd6a0 !important; + --jse-value-color-number: #f7b267 !important; + --jse-value-color-boolean: #f47067 !important; + --jse-value-color-null: #8b8d93 !important; + --jse-value-color-url: #8fd1ff !important; + --jse-delimiter-color: #55565c !important; + --jse-edit-outline: 2px solid #6cb6ff !important; + + /* Ausgewählt/gehovert - hier lag der gemeldete Fehler: Ein editiertes + Feld bekommt eine eigene Box auf Basis von --jse-background-color + (siehe oben) statt einer transparenten Fläche; ohne die beiden + folgenden Variablen blieb außerdem eine früher ausgewählte, aber + gerade inaktive Zeile hell. */ + --jse-contents-background-color: transparent !important; + --jse-selection-background-color: #2a3340 !important; + --jse-selection-background-inactive-color: #20222a !important; + --jse-hover-background-color: #22232a !important; + --jse-active-line-background-color: rgba(255,255,255,0.04) !important; + --jse-search-match-background-color: rgba(255,214,51,0.30) !important; + --jse-collapsed-items-background-color: #1c1d21 !important; --jse-collapsed-items-selected-background-color: #24252b !important; --jse-collapsed-items-link-color: #6cb6ff !important; --jse-collapsed-items-link-color-highlight: #8fcaff !important; - --jse-collapsed-items-selected-link-color: #ffffff !important; - --jse-delimiter-color: #55565c !important; - --jse-edit-outline: 2px solid #6cb6ff !important; - --jse-messages-info-background: #1c1d21 !important; - --jse-messages-info-color: #8fcaff !important; - --jse-messages-success-background: #123321 !important; - --jse-messages-success-color: #6bd8a1 !important; - --jse-messages-error-background: #3a1414 !important; - --jse-messages-error-color: #ff9a9a !important; - --jse-error-color: #e5484d !important; - --jse-warning-color: #f7b267 !important; + + --jse-search-match-color: #16171a !important; + --jse-search-match-outline: 2px solid #ffd633 !important; + --jse-search-match-active-color: #16171a !important; + + --jse-tag-background: #24252b !important; + --jse-tag-color: #e7e8ea !important; + + /* Tabellen-Ansicht */ + --jse-table-header-background: #1c1d21 !important; + --jse-table-header-background-highlight: #24252b !important; + --jse-table-row-odd-background: rgba(255,255,255,0.03) !important; + + /* Eingaben und Buttons in den eigenen Dialogen des Editors */ + --jse-input-background: #101114 !important; + --jse-input-background-readonly: rgba(255,255,255,0.03) !important; + --jse-input-color: #e7e8ea !important; + --jse-input-border: 1px solid #2a2b30 !important; + --jse-input-border-focus: 1px solid #6cb6ff !important; + --jse-button-background: #26272d !important; + --jse-button-background-highlight: #33343b !important; + --jse-button-color: #e7e8ea !important; + --jse-button-secondary-background: #26272d !important; + --jse-button-secondary-background-highlight: #33343b !important; + --jse-button-secondary-color: #e7e8ea !important; --jse-a-color: #6cb6ff !important; + --jse-a-color-highlight: #8fcaff !important; + + /* Meldungen (Fehler/Erfolg/Warnung/Info-Banner im Editor) */ + --jse-message-info-background: #1c1d21 !important; + --jse-message-info-color: #8fcaff !important; + --jse-message-success-background: #163524 !important; + --message-success-background: #163524 !important; + --jse-message-success-color: #6bd8a1 !important; + --jse-message-warning-background: #3a2f10 !important; + --jse-message-warning-color: #f7c948 !important; + --jse-message-error-background: #3a1414 !important; + --jse-message-error-color: #ff9a9a !important; + --jse-message-action-background: rgba(255,255,255,0.12) !important; + --jse-message-action-background-highlight: rgba(255,255,255,0.2) !important; + + --jse-svelte-select-background: #16171a !important; + background-color: #16171a !important; color: #e7e8ea !important; } @@ -439,6 +490,52 @@ .btn-mini:hover { border-color: var(--accent); color: var(--accent); } .btn-mini.danger:hover { border-color: var(--danger); color: var(--danger); } + /* Spalten-Typ-Editor im SQL-Dialog */ + .coltype-editor { + border: 1px solid var(--border-color); border-radius: 8px; + padding: 10px 14px; margin-bottom: 16px; background: var(--panel-bg); + } + .coltype-editor summary { + cursor: pointer; font-weight: 700; font-size: 13px; padding: 4px 0; + list-style: none; + } + .coltype-editor summary::-webkit-details-marker { display: none; } + .coltype-editor summary::before { content: '\25b8\0020'; } + .coltype-editor[open] summary::before { content: '\25be\0020'; } + .coltype-row { + display: flex; align-items: flex-start; gap: 10px; flex-wrap: wrap; + padding: 10px 0; border-top: 1px solid var(--border-color); + } + .coltype-name { font-weight: 700; font-size: 12.5px; min-width: 130px; padding-top: 7px; flex-shrink: 0; } + .coltype-badge { + font-size: 10px; font-weight: 700; color: var(--accent); background: rgba(255,122,26,0.12); + border-radius: 10px; padding: 1px 7px; margin-left: 4px; white-space: nowrap; + } + .coltype-row select { flex: 1 1 190px; min-width: 170px; } + .coltype-row input.coltype-rename { flex: 1 1 150px; min-width: 130px; } + .coltype-row input.coltype-length { width: 110px; flex-shrink: 0; } + .coltype-row select, .coltype-row input[type="text"] { + padding: 6px 8px; border-radius: 6px; border: 1px solid var(--border-color); + background: var(--card-bg); color: var(--text-color); font-size: 12.5px; font-family: inherit; + } + .coltype-row select:focus, .coltype-row input:focus { outline: none; border-color: var(--accent); } + .coltype-row input:disabled { opacity: 0.45; } + .coltype-hint { flex-basis: 100%; font-size: 11.5px; color: var(--text-muted); line-height: 1.5; margin-top: 2px; } + + /* Primärschlüssel-Auswahl (auch mehrere Spalten = zusammengesetzter Schlüssel) */ + .pk-checklist { + display: flex; flex-wrap: wrap; gap: 8px 16px; margin-bottom: 8px; + padding: 10px 12px; border: 1px solid var(--border-color); border-radius: 8px; background: var(--panel-bg); + } + .pk-check { display: flex; align-items: center; gap: 6px; font-size: 12.5px; cursor: pointer; white-space: nowrap; } + .pk-check input { width: auto; margin: 0; accent-color: var(--accent); cursor: pointer; } + + /* Zuletzt geöffnete Dateien im Öffnen-Menü */ + .recent-file-item { + padding: 6px 14px; font-size: 11.5px; color: var(--text-muted); + display: flex; align-items: center; gap: 7px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + } + kbd { background: var(--panel-bg); border: 1px solid var(--border-color); border-bottom-width: 2px; border-radius: 5px; padding: 2px 7px; font-family: "SFMono-Regular", Consolas, monospace; @@ -554,6 +651,7 @@
🌐 Von URL laden… 🗃️ Aus Datenbank laden… + @@ -589,6 +687,7 @@ + @@ -642,6 +741,7 @@ 🌐 Von URL laden… 🗃️ Aus Datenbank laden… + @@ -677,6 +777,7 @@ + @@ -795,7 +896,9 @@ const DEFAULT_SETTINGS = { webhookUrl: '', xmlRootName: 'root', xmlItemName: 'item', - splitRatio: 0.5 + splitRatio: 0.5, + autoSaveEnabled: false, + autoSaveIntervalMinutes: 10 }; function loadSettings() { @@ -810,6 +913,42 @@ function saveSettings(s) { } window.wbSettings = loadSettings(); +/* Merkt sich Primärschlüssel-Wahl, manuelle Spaltentypen und Spalten-Umbenennungen + aus dem MariaDB-Dialog getrennt für den linken und rechten Editor, damit sie + beim erneuten Öffnen des Dialogs für dasselbe Dokument erhalten bleiben. */ +window.sqlColumnPrefs = {}; +function loadSqlPrefs(side) { + if (window.sqlColumnPrefs[side]) return window.sqlColumnPrefs[side]; + try { + const raw = localStorage.getItem('wb_sql_prefs_' + side); + if (raw) { + const parsed = JSON.parse(raw); + // Ältere Version speicherte "pk" als einzelnen Spaltennamen statt als Liste. + if (typeof parsed.pk === 'string') parsed.pk = [parsed.pk]; + if (!Array.isArray(parsed.pk)) parsed.pk = []; + if (!parsed.overrides) parsed.overrides = {}; + if (!parsed.renames) parsed.renames = {}; + window.sqlColumnPrefs[side] = parsed; + return parsed; + } + } catch (e) { /* ignoriert */ } + window.sqlColumnPrefs[side] = { pk: [], overrides: {}, renames: {} }; + return window.sqlColumnPrefs[side]; +} +function saveSqlPrefs(side) { + try { localStorage.setItem('wb_sql_prefs_' + side, JSON.stringify(window.sqlColumnPrefs[side])); } + catch (e) { /* ignoriert */ } +} +/** Aktuellen Stand des Dialogs (Primärschlüssel, Typen, Umbenennungen) sichern. */ +function persistSqlPrefs(st) { + window.sqlColumnPrefs[st.side] = { pk: st.pk, overrides: st.overrides, renames: st.renames }; + saveSqlPrefs(st.side); +} +window.resetSqlPrefs = function(side) { + window.sqlColumnPrefs[side] = { pk: [], overrides: {}, renames: {} }; + try { localStorage.removeItem('wb_sql_prefs_' + side); } catch (e) { /* ignoriert */ } +}; + function escapeHtml(str) { return String(str).replace(/&/g, '&').replace(//g, '>') .replace(/"/g, '"').replace(/'/g, '''); @@ -873,13 +1012,51 @@ if (savedTitleRight) document.getElementById('titleRight').innerText = savedTitl const savedDarkMode = localStorage.getItem('wb_dark_mode'); const isDarkDefault = savedDarkMode !== null ? savedDarkMode === 'true' : true; +window.lastSelectionPath = { left: null, right: null }; + +/** Wandelt einen JSON-Pfad (Liste aus Schl\u00fcsseln/Indizes) in eine lesbare + * Notation wie "Positionen[2].Preis" um, wie sie in n8n-Ausdr\u00fccken \u00fcblich ist. */ +function formatJsonPath(path) { + if (!path || path.length === 0) return '(Wurzel)'; + let out = ''; + path.forEach((seg, i) => { + if (typeof seg === 'number') out += `[${seg}]`; + else if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(String(seg))) out += (i === 0 ? seg : '.' + seg); + else out += `["${String(seg).replace(/"/g, '\\"')}"]`; + }); + return out; +} + +function selectionToPath(selection) { + if (!selection) return null; + if (Array.isArray(selection.path)) return selection.path; + if (Array.isArray(selection.paths) && selection.paths.length) return selection.paths[0]; + if (Array.isArray(selection.focusPath)) return selection.focusPath; + return null; +} + +window.copyJsonPath = function(side) { + const path = window.lastSelectionPath[side]; + if (!path) return window.showToast('Bitte zuerst ein Feld im Baum anklicken.', 'error'); + const text = formatJsonPath(path); + navigator.clipboard.writeText(text).then(() => window.showToast('Pfad kopiert: ' + text, 'success')); +}; + window.editorLeft = createJSONEditor({ target: document.getElementById('jsoneditorLeft'), - props: { content: loadSavedContent('wb_left_content'), mode: 'tree', onChange: () => window.saveAppState() } + props: { + content: loadSavedContent('wb_left_content'), mode: 'tree', + onChange: () => window.saveAppState(), + onSelect: (selection) => { window.lastSelectionPath.left = selectionToPath(selection); } + } }); window.editorRight = createJSONEditor({ target: document.getElementById('jsoneditorRight'), - props: { content: loadSavedContent('wb_right_content'), mode: 'tree', onChange: () => window.saveAppState() } + props: { + content: loadSavedContent('wb_right_content'), mode: 'tree', + onChange: () => window.saveAppState(), + onSelect: (selection) => { window.lastSelectionPath.right = selectionToPath(selection); } + } }); window.activeSide = 'left'; @@ -925,6 +1102,46 @@ window.applyTheme = function(isDark) { window.cycleTheme = function() { window.applyTheme(!document.body.classList.contains('dark-mode')); }; window.applyTheme(isDarkDefault); +// "Zuletzt geöffnet"-Liste beim Start einmal befüllen +renderRecentFiles('left'); +renderRecentFiles('right'); + +/* ========================================================= + Automatische Sicherung als Download (optional, standardmäßig aus) + ========================================================= */ + +window.autoSaveState = { left: { lastHash: null }, right: { lastHash: null } }; +window.autoSaveTimer = null; + +function autoSaveTick() { + ['left', 'right'].forEach(side => { + const data = window.getJsonData(side); + if (data === null) return; // leer oder kaputtes JSON - nichts zu sichern + let text; + try { text = JSON.stringify(data); } catch (e) { return; } + if (text === '{}' || text === '[]' || text === '""') return; // praktisch leer + + const state = window.autoSaveState[side]; + if (state.lastHash === text) return; // seit der letzten Sicherung nichts geändert + state.lastHash = text; + + const stamp = new Date().toISOString().replace(/[:T]/g, '-').slice(0, 16); + const filename = docName(side) + '_autosave_' + stamp + '.json'; + downloadBlob(JSON.stringify(data, null, 2), filename, 'application/json'); + window.showToast('Automatisch gesichert: ' + filename); + }); +} + +/** Startet/stoppt den Hintergrund-Timer passend zu den aktuellen Einstellungen. + * Wird beim Start und nach jeder Änderung in den Einstellungen aufgerufen. */ +window.restartAutoSaveTimer = function() { + if (window.autoSaveTimer) { clearInterval(window.autoSaveTimer); window.autoSaveTimer = null; } + if (!window.wbSettings.autoSaveEnabled) return; + const minutes = Math.max(1, parseInt(window.wbSettings.autoSaveIntervalMinutes, 10) || 10); + window.autoSaveTimer = setInterval(autoSaveTick, minutes * 60 * 1000); +}; +window.restartAutoSaveTimer(); + /* =========================================================== 4. Daten-Helfer =========================================================== */ @@ -965,7 +1182,54 @@ function readFileAsText(file, callback) { reader.readAsText(file); } -window.clearEditor = function(side) { ed(side).set({ text: '' }); window.saveAppState(); }; +/** Setzt den Dokumentnamen oben auf den Dateinamen ohne Endung, damit sich + * eine geöffnete Datei nach dem Bearbeiten direkt wieder unter demselben + * Namen speichern lässt, und merkt sie sich in der "Zuletzt geöffnet"-Liste. */ +function setTitleFromFilename(side, filename) { + const base = filename.replace(/\.[^./\\]+$/, '').trim(); + if (base) { + document.getElementById(titleId(side)).innerText = base; + window.saveAppState(); + } + recordRecentFile(side, filename); +} + +function fileIconFor(filename) { + const ext = (filename.split('.').pop() || '').toLowerCase(); + const map = { json: '\u{1F4C4}', csv: '\u{1F4CA}', xlsx: '\u{1F4C8}', xls: '\u{1F4C8}', + yaml: '\u{1F4D1}', yml: '\u{1F4D1}', xml: '\u{1F9EC}', sql: '\u{1F5C3}\uFE0F' }; + return map[ext] || '\u{1F4C4}'; +} + +/** Merkt sich die letzten 5 pro Editor geöffneten Dateinamen (nur der Name, + * nicht der Inhalt) als kleine Erinnerungsstütze im "Öffnen"-Menü. */ +function recordRecentFile(side, filename) { + const key = 'wb_recent_' + side; + let list = []; + try { list = JSON.parse(localStorage.getItem(key) || '[]'); } catch (e) { list = []; } + list = list.filter(f => f.name !== filename); + list.unshift({ name: filename, date: new Date().toLocaleString('de-DE', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }) }); + list = list.slice(0, 5); + try { localStorage.setItem(key, JSON.stringify(list)); } catch (e) { /* ignoriert */ } + renderRecentFiles(side); +} + +function renderRecentFiles(side) { + const container = document.getElementById(side === 'right' ? 'recentFilesRight' : 'recentFilesLeft'); + if (!container) return; + let list = []; + try { list = JSON.parse(localStorage.getItem('wb_recent_' + side) || '[]'); } catch (e) { list = []; } + if (list.length === 0) { container.innerHTML = ''; return; } + container.innerHTML = '| id (PK, automatisch) | '; + colNames.forEach(c => { + const marker = pkList.includes(c) ? ' 🔑 PK' : ''; + const renamed = outNames[c] !== c; + const label = renamed + ? escapeHtml(outNames[c]) + ' umbenannt' + : escapeHtml(c); + html += '' + label + ' (' + columns[c] + ')' + marker + ' | '; + }); + html += '
|---|---|
| ' + (idx + 1) + ' | '; + colNames.forEach(c => { + const v = item[c]; + const d = (typeof v === 'object' && v !== null) ? JSON.stringify(v) : String(v === undefined || v === null ? '' : v); + html += '' + escapeHtml(d) + ' | '; + }); + html += '
id-Spalte (Standard).
+ Ein Haken – diese Spalte wird zum Primärschlüssel; achte darauf, dass sie
+ in jeder Zeile eindeutig und nicht leer ist. Mehrere Haken – zusammengesetzter
+ Schlüssel, der erst in Kombination eindeutig sein muss. Die Auswahl wird für
+ dieses Dokument gemerkt, bis du oben auf „Neu“ klickst.
+ ${escapeHtml(sql)}`;
+}
+
+window.onSqlPkToggle = function(colName, checked) {
+ const st = window.sqlModalState;
+ if (checked) { if (!st.pk.includes(colName)) st.pk.push(colName); }
+ else { st.pk = st.pk.filter(c => c !== colName); }
+ persistSqlPrefs(st);
+ renderSqlModalBody();
+};
+
+window.onColumnTypeChange = function(colName, base) {
+ const st = window.sqlModalState;
+ const info = SQL_TYPE_CATALOG[base];
+ st.overrides[colName] = { base, length: info.hasLength ? info.lengthDefault : '' };
+ st.editorOpen = true;
+ persistSqlPrefs(st);
+ renderSqlModalBody();
+};
+
+window.onColumnLengthChange = function(colName, length) {
+ const st = window.sqlModalState;
+ const ov = st.overrides[colName];
+ if (ov) ov.length = length;
+ st.editorOpen = true;
+ persistSqlPrefs(st);
+ renderSqlModalBody();
+};
+
+window.onColumnRenameChange = function(colName, value) {
+ const st = window.sqlModalState;
+ const sanitized = sanitizeColumnName(value);
+ if (!sanitized || sanitized === colName) delete st.renames[colName];
+ else st.renames[colName] = sanitized;
+ st.editorOpen = true;
+ persistSqlPrefs(st);
+ renderSqlModalBody();
+};
+
window.openSqlModal = function(side) {
const data = window.getJsonData(side);
if (data === null) return window.showToast('Bitte zuerst g\u00fcltige Daten in den Editor laden.', 'error');
const tableName = docName(side).toLowerCase().replace(/[^a-z0-9_]/g, '_');
const items = toRows(data);
- const { sql, columns, colNames } = buildSql(tableName, items);
+ const { columns, colNames } = inferColumns(items);
- let htmlTable = '| id (PK) | '; - colNames.forEach(c => { htmlTable += '' + escapeHtml(c) + ' (' + columns[c] + ') | '; }); - htmlTable += '
|---|---|
| ' + (idx + 1) + ' | '; - colNames.forEach(c => { - const v = item[c]; - const d = (typeof v === 'object' && v !== null) ? JSON.stringify(v) : String(v === undefined || v === null ? '' : v); - htmlTable += '' + escapeHtml(d) + ' | '; - }); - htmlTable += '
${escapeHtml(sql)}`;
+ window.sqlModalState = { side, tableName, items, columns, colNames, pk, overrides, renames, editorOpen: false, sql: '' };
- window.openModal('🗃️ MariaDB Workbench: ' + escapeHtml(tableName), body, [
+ window.openModal('🗃️ MariaDB Workbench: ' + escapeHtml(tableName), 'bestellung_autosave_2026-09-17-1530.json).
+ Nur ein zusätzliches Sicherheitsnetz – ersetzt nicht das gezielte Speichern per
+ „Export“, und die heruntergeladenen Dateien sammeln sich im Download-Ordner an,
+ du räumst sie also gelegentlich selbst auf.
+ Der Name oben links („Dokument 1“) wird beim Export zum Dateinamen und beim - SQL-Export zum Tabellennamen. Vergib also am besten gleich einen sprechenden Namen.
+ SQL-Export zum Tabellennamen. Vergib also am besten gleich einen sprechenden Namen – + beim Öffnen einer Datei geschieht das ohnehin automatisch: Der Name wird auf den + Dateinamen ohne Endung gesetzt, damit sich eine bearbeitete Datei direkt wieder unter + demselben Namen speichern lässt. Im Öffnen-Menü findest du außerdem die + letzten fünf Dateien, die du in diesem Editor geöffnet hast – nur als + Gedächtnisstütze, ein erneutes Laden ersetzt das nicht. + +Ein Klick auf ein Feld im Baum, dann oben auf „Pfad“ – kopiert eine
+ Notation wie Positionen[2].Preis in die Zwischenablage. Praktisch, um schnell
+ an einen n8n-Ausdruck oder eine Doku-Notiz zu kommen, ohne den Pfad von Hand abzuzählen.
Inhalte, Namen, Einstellungen, Snippets und Datenbank-Profile liegen im lokalen Speicher deines Browsers. Nach einem Neustart ist alles wieder da. Es wird nichts an einen fremden Server gesendet – außer du benutzt bewusst URL-Import, Webhook oder Datenbank.
Weil der Speicher am Browser hängt: Beim Wechsel auf einen anderen Rechner nimmst du - deine Konfiguration über Einstellungen → Sicherung exportieren mit.
+ deine Konfiguration über Einstellungen → Sicherung exportieren mit. Wer mag, + schaltet dort zusätzlich eine automatische Sicherung als Download in festem Abstand ein + (standardmäßig aus) – ein zusätzliches Sicherheitsnetz, kein Ersatz für + das gezielte Speichern.id und wert.@ werden wieder zu Attributen. Wurzel- und Listen-Tag stellst du in den Einstellungen ein.CREATE TABLE samt INSERTs. Die Spaltentypen werden aus den Werten geraten; prüfe sie, bevor du das Skript produktiv einspielst.CREATE TABLE samt INSERTs. Die Spaltentypen werden aus den Werten geraten, inklusive automatischer Unterscheidung zwischen INT und dem größeren BIGINT bei sehr großen Zahlen – passt eine Zuordnung trotzdem nicht, lässt sie sich im Bereich „Spalten anpassen“ gezielt überschreiben, inklusive Längenangabe, Umbenennung und Erklärung zu jedem Typ. Standardmäßig bekommt die Tabelle eine eigene, fortlaufende id-Spalte als Primärschlüssel – per Häkchen lässt sich stattdessen eine vorhandene Spalte dazu machen, oder mehrere zusammen als zusammengesetzter Schlüssel.Veröffentlicht unter der MIT-Lizenz. Du darfst sie frei verwenden, verändern und weitergeben, solange der Copyright- und Lizenzhinweis erhalten bleibt. Sie wird ohne Gewährleistung bereitgestellt.
-Quellcode: im Repository hinterlegt - – die Adresse trägst du hier im Quelltext ein, sobald das Repository steht.
+Quellcode: git.obraasch.de/olli1986/JSON-Studio