From dc2c21a1e813c8bfd0f30f3d871075cfec251117 Mon Sep 17 00:00:00 2001 From: Hardeep Asrani Date: Mon, 16 Mar 2026 07:06:02 +0530 Subject: [PATCH] feat: replace manual config textarea with CodeMirror JSON editor Initialize CodeMirror on the manual configuration textarea with syntax highlighting, line numbers, bracket matching, and auto-close brackets Closes #509 --- classes/Visualizer/Module/Chart.php | 1 + classes/Visualizer/Render/Sidebar.php | 2 +- classes/Visualizer/Render/Sidebar/ChartJS.php | 2 +- css/frame.css | 55 +++++++++++++++++++ js/preview.js | 37 ++++++++++++- 5 files changed, 93 insertions(+), 4 deletions(-) diff --git a/classes/Visualizer/Module/Chart.php b/classes/Visualizer/Module/Chart.php index aaee18cc7..f67020e5c 100644 --- a/classes/Visualizer/Module/Chart.php +++ b/classes/Visualizer/Module/Chart.php @@ -853,6 +853,7 @@ private function _handleDataAndSettingsPage() { wp_enqueue_script( 'visualizer-preview' ); wp_enqueue_script( 'visualizer-chosen' ); wp_enqueue_script( 'visualizer-render' ); + wp_enqueue_code_editor( array( 'type' => 'application/json' ) ); if ( Visualizer_Module::can_show_feature( 'simple-editor' ) ) { wp_enqueue_script( 'visualizer-editor-simple' ); diff --git a/classes/Visualizer/Render/Sidebar.php b/classes/Visualizer/Render/Sidebar.php index 5e13bd01f..6d5031971 100644 --- a/classes/Visualizer/Render/Sidebar.php +++ b/classes/Visualizer/Render/Sidebar.php @@ -142,7 +142,7 @@ protected function _renderManualConfigDescription() { self::_renderSectionDescription( '' . sprintf( // translators: %1$s - HTML link tag, %2$s - HTML closing link tag, %3$s - HTML link tag, %4$s - HTML closing link tag. - __( 'Configure the graph by providing configuration variables right from the %1$sGoogle Visualization API%2$s. You can refer to to some examples %3$shere%4$s.', 'visualizer' ), '', + __( 'Configure the graph by providing configuration variables right from the %1$sGoogle Visualization API%2$s. You can refer to %3$sCommon Snippets%4$s for examples.', 'visualizer' ), '', '', '', '' diff --git a/classes/Visualizer/Render/Sidebar/ChartJS.php b/classes/Visualizer/Render/Sidebar/ChartJS.php index 1323e6c2e..009f53e31 100644 --- a/classes/Visualizer/Render/Sidebar/ChartJS.php +++ b/classes/Visualizer/Render/Sidebar/ChartJS.php @@ -390,7 +390,7 @@ protected function _renderManualConfigDescription() { self::_renderSectionDescription( '' . sprintf( // translators: %1$s - HTML link tag, %2$s - HTML closing link tag, %3$s - HTML link tag, %4$s - HTML closing link tag. - __( 'Configure the graph by providing configuration variables right from the %1$sChartJS API%2$s. You can refer to to some examples %3$shere%4$s.', 'visualizer' ), + __( 'Configure the graph by providing configuration variables right from the %1$sChartJS API%2$s. You can refer to %3$sCommon Snippets%4$s for examples.', 'visualizer' ), '', '', '', diff --git a/css/frame.css b/css/frame.css index 1d36c989d..b4fae0551 100644 --- a/css/frame.css +++ b/css/frame.css @@ -1481,6 +1481,61 @@ span.viz-section-error { } +/******************************************************************************/ +/************************ manual config JSON editor *************************/ +/******************************************************************************/ +textarea[name="manual"] + .CodeMirror { + background: #282923; + color: #f8f8f2; + border-radius: 4px; + height: 200px; + font-size: 13px; +} + +textarea[name="manual"] + .CodeMirror .CodeMirror-scroll { + min-height: 200px; +} + +textarea[name="manual"] + .CodeMirror .CodeMirror-gutters { + background: #1e1f1c; + border-right: 1px solid #404040; + color: #75715e; +} + +textarea[name="manual"] + .CodeMirror .CodeMirror-linenumber { + color: #75715e; +} + +textarea[name="manual"] + .CodeMirror .CodeMirror-cursor { + border-left: 1px solid #f8f8f0; +} + +textarea[name="manual"] + .CodeMirror .CodeMirror-selected { + background: #49483e; +} + +textarea[name="manual"] + .CodeMirror pre.CodeMirror-line, +textarea[name="manual"] + .CodeMirror pre.CodeMirror-line-like { + color: #f8f8f2; +} + +textarea[name="manual"] + .CodeMirror .cm-string { + color: #A9DC76; +} + +textarea[name="manual"] + .CodeMirror .cm-number { + color: #AB9DF2; +} + +textarea[name="manual"] + .CodeMirror .cm-atom { + color: #78DCE8; +} + +textarea[name="manual"] + .CodeMirror .cm-punctuation, +textarea[name="manual"] + .CodeMirror .cm-bracket { + color: #f8f8f2; +} + /******************************************************************************/ /******************************** data tables *******************************/ /******************************************************************************/ diff --git a/js/preview.js b/js/preview.js index 90768cf51..8e542b0f5 100644 --- a/js/preview.js +++ b/js/preview.js @@ -2,6 +2,7 @@ /* global console */ /* global vizSettingsHaveChanged */ /* global vizHaveSettingsChanged */ +/* global wp */ (function($, v) { var timeout; @@ -22,6 +23,7 @@ clear: updateChart }); $('#settings-form textarea[name="manual"]').change(validateJSON).keyup(validateJSON); + initManualConfigEditor(); vizSettingsHaveChanged(false); }; @@ -173,12 +175,42 @@ function validateJSON() { $('#visualizer-error-manual').remove(); try{ - var options = JSON.parse($(this).val()); + JSON.parse($(this).val()); }catch(error){ - $('
Invalid JSON: ' + error + '
').insertAfter($(this)); + var $after = $(this).next('.CodeMirror').length ? $(this).next('.CodeMirror') : $(this); + $('
Invalid JSON: ' + error + '
').insertAfter($after); } } + function initManualConfigEditor() { + var $textarea = $('textarea[name="manual"]'); + if (!$textarea.length || $textarea.data('cm-initialized')) return; + + var CodeMirrorLib = (typeof wp !== 'undefined' && wp.CodeMirror) ? wp.CodeMirror : null; + if (!CodeMirrorLib) return; + + $textarea.data('cm-initialized', true); + + var cm = CodeMirrorLib.fromTextArea($textarea.get(0), { + mode: 'application/json', + lineNumbers: true, + lineWrapping: true, + matchBrackets: true, + autoCloseBrackets: true + }); + + // Refresh when any sidebar group is expanded so line numbers render correctly + // (CodeMirror can't measure gutter width while the container is hidden). + $(document).on('click.vizManualConfig', '.viz-group-title', function() { + setTimeout(function() { cm.refresh(); }, 50); + }); + + cm.on('change', function() { + cm.save(); + $textarea.trigger('change'); + }); + } + $('.control-text').change(updateChart).keyup(updateChart); $('.control-select, .control-checkbox, .control-check').change(updateChart); $('.color-picker-hex').wpColorPicker({ @@ -186,6 +218,7 @@ clear: updateChart }); $('textarea[name="manual"]').change(validateJSON).keyup(validateJSON); + initManualConfigEditor(); }); })(jQuery, visualizer);