Last active
October 27, 2025 13:10
-
-
Save watermusic/9232d4fe21fcee14219f9892542aea93 to your computer and use it in GitHub Desktop.
JSON Syntax Validator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name JSON Syntax Validator | |
| // @namespace http://tampermonkey.net/ | |
| // @version 1.0.2 | |
| // @description Validiert JSON-Syntax in spezifischen PIM Textarea-/Input Feldern | |
| // @match https://pim.ppapi.de/* | |
| // @updateURL https://gist.githubusercontent.com/watermusic/9232d4fe21fcee14219f9892542aea93/raw/ff457d4107e4a0f2b254663a65864409c51f8d6b/json-validator.pim.js | |
| // @downloadURL https://gist.githubusercontent.com/watermusic/9232d4fe21fcee14219f9892542aea93/raw/ff457d4107e4a0f2b254663a65864409c51f8d6b/json-validator.pim.js | |
| // @grant none | |
| // ==/UserScript== | |
| (function() { | |
| 'use strict'; | |
| // Konfiguration: Liste der Selektoren für Textareas die validiert werden sollen | |
| const TEXTAREA_SELECTORS = [ | |
| 'div[data-attribute="graduated_price"] textarea', | |
| 'div[data-attribute="design_picker_attributes"] input', | |
| 'div[data-attribute="print_placements"] textarea', | |
| 'div[data-attribute="printess_start_design"] input', | |
| 'div[data-attribute="cpp_start_design_id"] textarea', | |
| 'div[data-attribute="printess_layout_snippet"] input', | |
| 'div[data-attribute="printess_group_snippets"] input', | |
| 'div[data-attribute="mockup_form_fields"] textarea', | |
| 'div[data-attribute="supplier_production_parameters"] textarea', | |
| ]; | |
| // CSS Styles hinzufügen | |
| const style = document.createElement('style'); | |
| style.textContent = ` | |
| .json-valid { | |
| border: 3px solid #28a745 !important; | |
| box-shadow: 0 0 5px rgba(40, 167, 69, 0.5) !important; | |
| } | |
| .json-invalid { | |
| border: 3px solid #dc3545 !important; | |
| box-shadow: 0 0 5px rgba(220, 53, 69, 0.5) !important; | |
| } | |
| .json-error-tooltip { | |
| position: absolute; | |
| background: #dc3545; | |
| color: white; | |
| padding: 8px 12px; | |
| border-radius: 4px; | |
| font-size: 12px; | |
| z-index: 10000; | |
| max-width: 300px; | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.3); | |
| } | |
| .json-error-tooltip::before { | |
| content: ''; | |
| position: absolute; | |
| top: -5px; | |
| left: 10px; | |
| border-left: 5px solid transparent; | |
| border-right: 5px solid transparent; | |
| border-bottom: 5px solid #dc3545; | |
| } | |
| `; | |
| document.head.appendChild(style); | |
| function validateJSON(textarea) { | |
| // Vorherige Klassen entfernen | |
| textarea.classList.remove('json-valid', 'json-invalid'); | |
| // Bestehende Tooltips entfernen | |
| const existingTooltip = document.querySelector('.json-error-tooltip'); | |
| if (existingTooltip) { | |
| existingTooltip.remove(); | |
| } | |
| const content = textarea.value.trim(); | |
| if (!content) { | |
| return; // Leere Felder nicht validieren | |
| } | |
| try { | |
| // JSON parsen - das reicht für Syntax-Validierung | |
| const jsonData = JSON.parse(content); | |
| textarea.classList.add('json-valid'); | |
| console.log('✅ JSON Syntax ist korrekt:', jsonData); | |
| } catch (parseError) { | |
| // JSON Parse Fehler | |
| textarea.classList.add('json-invalid'); | |
| // Detaillierte Fehlermeldung erstellen | |
| let errorMessage = parseError.message; | |
| // Häufige Fehler benutzerfreundlicher machen | |
| if (errorMessage.includes('Unexpected token')) { | |
| if (errorMessage.includes('Unexpected token ,')) { | |
| errorMessage = 'Überflüssiges Komma gefunden'; | |
| } else if (errorMessage.includes('Unexpected token }')) { | |
| errorMessage = 'Fehlendes Komma vor der schließenden Klammer'; | |
| } else if (errorMessage.includes('Unexpected token ]')) { | |
| errorMessage = 'Fehlendes Komma vor der schließenden eckigen Klammer'; | |
| } else if (errorMessage.includes('Unexpected end')) { | |
| errorMessage = 'JSON unvollständig - fehlende schließende Klammer'; | |
| } | |
| } else if (errorMessage.includes('Unterminated string')) { | |
| errorMessage = 'Unvollständiger String - fehlende Anführungszeichen'; | |
| } else if (errorMessage.includes('Expected property name')) { | |
| errorMessage = 'Property-Name erwartet (in Anführungszeichen)'; | |
| } | |
| showErrorTooltip(textarea, [{message: errorMessage}]); | |
| console.log('❌ JSON Syntax Fehler:', parseError.message); | |
| } | |
| } | |
| function showErrorTooltip(textarea, errors) { | |
| const tooltip = document.createElement('div'); | |
| tooltip.className = 'json-error-tooltip'; | |
| // Einfach die erste Fehlermeldung anzeigen | |
| tooltip.textContent = errors[0].message; | |
| // Position relativ zur Textarea | |
| const rect = textarea.getBoundingClientRect(); | |
| tooltip.style.position = 'fixed'; | |
| tooltip.style.left = `${rect.left}px`; | |
| tooltip.style.top = `${rect.top - 40}px`; | |
| document.body.appendChild(tooltip); | |
| // Tooltip nach 5 Sekunden automatisch entfernen | |
| setTimeout(() => { | |
| if (tooltip.parentNode) { | |
| tooltip.remove(); | |
| } | |
| }, 5000); | |
| } | |
| function setupValidation() { | |
| let textareas = []; | |
| // Textareas anhand der definierten Selektoren finden | |
| TEXTAREA_SELECTORS.forEach(selector => { | |
| const found = document.querySelectorAll(selector); | |
| found.forEach(textarea => { | |
| if (!textareas.includes(textarea)) { | |
| textareas.push(textarea); | |
| } | |
| }); | |
| }); | |
| textareas.forEach(textarea => { | |
| // Event Listener für Echtzeit-Validierung | |
| textarea.addEventListener('input', debounce(() => { | |
| validateJSON(textarea); | |
| }, 500)); | |
| // Event Listener für Blur (wenn Feld verlassen wird) | |
| textarea.addEventListener('blur', () => { | |
| validateJSON(textarea); | |
| }); | |
| // Initial validieren falls schon Inhalt vorhanden | |
| if (textarea.value.trim()) { | |
| validateJSON(textarea); | |
| } | |
| }); | |
| console.log(`🔍 JSON Syntax Validator aktiv für ${textareas.length} spezifische Textarea(s)`); | |
| if (textareas.length === 0) { | |
| console.warn('⚠️ Keine Textareas mit den angegebenen Selektoren gefunden!'); | |
| console.log('Gesuchte Selektoren:', TEXTAREA_SELECTORS); | |
| } | |
| } | |
| function setupListener() { | |
| // Event-Listener für alle Klicks auf der Seite | |
| document.addEventListener('click', function(e) { | |
| // Hier kannst du spezifische Elemente filtern | |
| // z.B. nur <p>, <span>, <div> mit einer bestimmten Klasse | |
| const target = e.target; | |
| // Beispiel: Nur Elemente mit Klasse 'copyable' kopieren | |
| if (!target.classList.contains('breadcrumb-item')) { | |
| return; | |
| } | |
| const pimClass = "fouxYE"; | |
| target.classList.remove(pimClass); | |
| const text = target.innerText || target.textContent; | |
| target.classList.add(pimClass); | |
| if (text) { | |
| // Moderne Clipboard API | |
| navigator.clipboard.writeText(text).then(function() { | |
| console.log('Text kopiert:', text); | |
| // Optionales visuelles Feedback | |
| const originalBg = target.style.backgroundColor; | |
| target.style.backgroundColor = '#90EE90'; | |
| setTimeout(() => { | |
| target.style.backgroundColor = originalBg; | |
| }, 200); | |
| }).catch(function(err) { | |
| console.error('Fehler beim Kopieren:', err); | |
| }); | |
| } | |
| }); | |
| } | |
| // Debounce Funktion um zu häufige Validierung zu vermeiden | |
| function debounce(func, wait) { | |
| let timeout; | |
| return function executedFunction(...args) { | |
| const later = () => { | |
| clearTimeout(timeout); | |
| func(...args); | |
| }; | |
| clearTimeout(timeout); | |
| timeout = setTimeout(later, wait); | |
| }; | |
| } | |
| // Auf dynamisch hinzugefügte Textareas reagieren | |
| function observeDOM() { | |
| const observer = new MutationObserver(mutations => { | |
| mutations.forEach(mutation => { | |
| mutation.addedNodes.forEach(node => { | |
| if (node.nodeType === Node.ELEMENT_NODE) { | |
| // Prüfen ob hinzugefügte Nodes unsere Selektoren matchen | |
| TEXTAREA_SELECTORS.forEach(selector => { | |
| let matchingElements = []; | |
| if (node.matches && node.matches(selector)) { | |
| matchingElements.push(node); | |
| } | |
| if (node.querySelectorAll) { | |
| const found = node.querySelectorAll(selector); | |
| matchingElements.push(...found); | |
| } | |
| matchingElements.forEach(setupValidationForElement); | |
| }); | |
| } | |
| }); | |
| }); | |
| }); | |
| observer.observe(document.body, { | |
| childList: true, | |
| subtree: true | |
| }); | |
| } | |
| function setupValidationForElement(textarea) { | |
| // Verhindere doppelte Event Listener | |
| if (textarea.dataset.jsonValidatorActive) return; | |
| textarea.dataset.jsonValidatorActive = 'true'; | |
| textarea.addEventListener('input', debounce(() => { | |
| validateJSON(textarea); | |
| }, 500)); | |
| textarea.addEventListener('blur', () => { | |
| validateJSON(textarea); | |
| }); | |
| if (textarea.value.trim()) { | |
| validateJSON(textarea); | |
| } | |
| } | |
| // Warten bis DOM geladen ist | |
| if (document.readyState === 'loading') { | |
| document.addEventListener('DOMContentLoaded', () => { | |
| setupValidation(); | |
| setupListener(); | |
| observeDOM(); | |
| }); | |
| } else { | |
| setupValidation(); | |
| setupListener(); | |
| observeDOM(); | |
| } | |
| // Konsolen-Befehle für manuelle Kontrolle | |
| window.validateAllTextareas = () => { | |
| document.querySelectorAll('textarea').forEach(validateJSON); | |
| }; | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment