Skip to content

Commit e901194

Browse files
Fix scroll to top and restore masked API key display
Per user feedback - two critical fixes: 1. Enhanced Scroll Prevention (Types.php) - Removed for="chart-library" from label (prevents auto-focus scroll) - Added tabindex="-1" to select element (prevents focus-based scroll) - More aggressive scroll lock with 17 timeout intervals (0-2500ms) - Added document.body.scrollTop and documentElement.scrollTop resets - Added readystatechange listener for early DOM changes - Listens to both document and window scroll events - Extends lock to 3 seconds with console logging for debugging - Forces scroll on both iframe and parent window This should finally catch whatever async code is causing the scroll. 2. Restored Masked API Key Display (AISettings.php) - Removed green checkmark indicator (user feedback) - Keys now display masked: first 6 chars + asterisks + last 4 chars - Fields are readonly by default showing masked value - "Change Key" button makes field editable and clears it - "Cancel" button restores masked value and readonly state - Save only updates if value changed (not masked version) - Secure but provides visual confirmation that key exists 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 4e52b89 commit e901194

2 files changed

Lines changed: 90 additions & 31 deletions

File tree

classes/Visualizer/Render/Page/AISettings.php

Lines changed: 69 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,10 @@ protected function _renderContent() {
102102
$gemini_key = get_option( 'visualizer_gemini_api_key', '' );
103103
$claude_key = get_option( 'visualizer_claude_api_key', '' );
104104

105-
// Check if keys exist (for placeholder text)
106-
$has_openai_key = ! empty( $openai_key );
107-
$has_gemini_key = ! empty( $gemini_key );
108-
$has_claude_key = ! empty( $claude_key );
105+
// Mask the keys for display
106+
$openai_key_display = $this->_maskAPIKey( $openai_key );
107+
$gemini_key_display = $this->_maskAPIKey( $gemini_key );
108+
$claude_key_display = $this->_maskAPIKey( $claude_key );
109109

110110
echo '<form method="post" action="">';
111111
wp_nonce_field( 'visualizer_ai_settings', 'visualizer_ai_settings_nonce' );
@@ -116,11 +116,8 @@ protected function _renderContent() {
116116
echo '<tr>';
117117
echo '<th scope="row"><label for="visualizer_openai_api_key">' . esc_html__( 'OpenAI API Key (ChatGPT)', 'visualizer' ) . '</label></th>';
118118
echo '<td>';
119-
echo '<input type="password" id="visualizer_openai_api_key" name="visualizer_openai_api_key" value="" class="regular-text" placeholder="' . ( $has_openai_key ? esc_attr__( 'API key is set (enter new key to replace)', 'visualizer' ) : esc_attr__( 'Enter API key', 'visualizer' ) ) . '" autocomplete="off" />';
120-
if ( $has_openai_key ) {
121-
echo '<input type="hidden" name="visualizer_openai_api_key_exists" value="1" />';
122-
echo '<p class="description" style="color: #46b450; font-weight: 500;"><span class="dashicons dashicons-yes-alt" style="color: #46b450;"></span> ' . esc_html__( 'API key is configured', 'visualizer' ) . '</p>';
123-
}
119+
echo '<input type="text" id="visualizer_openai_api_key" name="visualizer_openai_api_key" value="' . esc_attr( $openai_key_display ) . '" class="regular-text" placeholder="' . esc_attr__( 'Enter API key', 'visualizer' ) . '" autocomplete="off" readonly />';
120+
echo '<button type="button" class="button visualizer-change-key" data-target="visualizer_openai_api_key" style="margin-left: 5px;">' . esc_html__( 'Change Key', 'visualizer' ) . '</button>';
124121
echo '<p class="description">' . esc_html__( 'Enter your OpenAI API key to enable ChatGPT integration.', 'visualizer' ) . ' <a href="https://platform.openai.com/api-keys" target="_blank">' . esc_html__( 'Get API Key', 'visualizer' ) . '</a></p>';
125122
echo '</td>';
126123
echo '</tr>';
@@ -129,11 +126,8 @@ protected function _renderContent() {
129126
echo '<tr>';
130127
echo '<th scope="row"><label for="visualizer_gemini_api_key">' . esc_html__( 'Google Gemini API Key', 'visualizer' ) . '</label></th>';
131128
echo '<td>';
132-
echo '<input type="password" id="visualizer_gemini_api_key" name="visualizer_gemini_api_key" value="" class="regular-text" placeholder="' . ( $has_gemini_key ? esc_attr__( 'API key is set (enter new key to replace)', 'visualizer' ) : esc_attr__( 'Enter API key', 'visualizer' ) ) . '" autocomplete="off" />';
133-
if ( $has_gemini_key ) {
134-
echo '<input type="hidden" name="visualizer_gemini_api_key_exists" value="1" />';
135-
echo '<p class="description" style="color: #46b450; font-weight: 500;"><span class="dashicons dashicons-yes-alt" style="color: #46b450;"></span> ' . esc_html__( 'API key is configured', 'visualizer' ) . '</p>';
136-
}
129+
echo '<input type="text" id="visualizer_gemini_api_key" name="visualizer_gemini_api_key" value="' . esc_attr( $gemini_key_display ) . '" class="regular-text" placeholder="' . esc_attr__( 'Enter API key', 'visualizer' ) . '" autocomplete="off" readonly />';
130+
echo '<button type="button" class="button visualizer-change-key" data-target="visualizer_gemini_api_key" style="margin-left: 5px;">' . esc_html__( 'Change Key', 'visualizer' ) . '</button>';
137131
echo '<p class="description">' . esc_html__( 'Enter your Google Gemini API key.', 'visualizer' ) . ' <a href="https://makersuite.google.com/app/apikey" target="_blank">' . esc_html__( 'Get API Key', 'visualizer' ) . '</a></p>';
138132
echo '</td>';
139133
echo '</tr>';
@@ -142,11 +136,8 @@ protected function _renderContent() {
142136
echo '<tr>';
143137
echo '<th scope="row"><label for="visualizer_claude_api_key">' . esc_html__( 'Anthropic Claude API Key', 'visualizer' ) . '</label></th>';
144138
echo '<td>';
145-
echo '<input type="password" id="visualizer_claude_api_key" name="visualizer_claude_api_key" value="" class="regular-text" placeholder="' . ( $has_claude_key ? esc_attr__( 'API key is set (enter new key to replace)', 'visualizer' ) : esc_attr__( 'Enter API key', 'visualizer' ) ) . '" autocomplete="off" />';
146-
if ( $has_claude_key ) {
147-
echo '<input type="hidden" name="visualizer_claude_api_key_exists" value="1" />';
148-
echo '<p class="description" style="color: #46b450; font-weight: 500;"><span class="dashicons dashicons-yes-alt" style="color: #46b450;"></span> ' . esc_html__( 'API key is configured', 'visualizer' ) . '</p>';
149-
}
139+
echo '<input type="text" id="visualizer_claude_api_key" name="visualizer_claude_api_key" value="' . esc_attr( $claude_key_display ) . '" class="regular-text" placeholder="' . esc_attr__( 'Enter API key', 'visualizer' ) . '" autocomplete="off" readonly />';
140+
echo '<button type="button" class="button visualizer-change-key" data-target="visualizer_claude_api_key" style="margin-left: 5px;">' . esc_html__( 'Change Key', 'visualizer' ) . '</button>';
150141
echo '<p class="description">' . esc_html__( 'Enter your Anthropic Claude API key.', 'visualizer' ) . ' <a href="https://console.anthropic.com/account/keys" target="_blank">' . esc_html__( 'Get API Key', 'visualizer' ) . '</a></p>';
151142
echo '</td>';
152143
echo '</tr>';
@@ -159,6 +150,45 @@ protected function _renderContent() {
159150

160151
echo '</form>';
161152

153+
// Add JavaScript to handle Change Key button
154+
?>
155+
<script type="text/javascript">
156+
jQuery(document).ready(function($) {
157+
$('.visualizer-change-key').on('click', function() {
158+
var $button = $(this);
159+
var targetId = $button.attr('data-target');
160+
var $input = $('#' + targetId);
161+
162+
// Make field editable and clear it
163+
$input.prop('readonly', false).val('').focus();
164+
165+
// Change button text
166+
$button.text('<?php echo esc_js( __( 'Cancel', 'visualizer' ) ); ?>');
167+
$button.removeClass('visualizer-change-key').addClass('visualizer-cancel-change');
168+
});
169+
170+
$(document).on('click', '.visualizer-cancel-change', function() {
171+
var $button = $(this);
172+
var targetId = $button.attr('data-target');
173+
var $input = $('#' + targetId);
174+
var originalValue = $input.attr('data-original');
175+
176+
// Restore readonly and original masked value
177+
$input.prop('readonly', true).val(originalValue || '');
178+
179+
// Change button text back
180+
$button.text('<?php echo esc_js( __( 'Change Key', 'visualizer' ) ); ?>');
181+
$button.removeClass('visualizer-cancel-change').addClass('visualizer-change-key');
182+
});
183+
184+
// Store original masked values
185+
$('input[type="text"][id^="visualizer_"]').each(function() {
186+
$(this).attr('data-original', $(this).val());
187+
});
188+
});
189+
</script>
190+
<?php
191+
162192
echo '</div>'; // End opacity wrapper
163193

164194
if ( $is_locked ) {
@@ -177,19 +207,33 @@ protected function _renderContent() {
177207
* @return void
178208
*/
179209
private function _saveSettings() {
180-
// Only update OpenAI key if a new value is provided
210+
// Get current keys
211+
$current_openai = get_option( 'visualizer_openai_api_key', '' );
212+
$current_gemini = get_option( 'visualizer_gemini_api_key', '' );
213+
$current_claude = get_option( 'visualizer_claude_api_key', '' );
214+
215+
// Only update OpenAI key if a new value is provided and it's not the masked version
181216
if ( isset( $_POST['visualizer_openai_api_key'] ) && ! empty( $_POST['visualizer_openai_api_key'] ) ) {
182-
update_option( 'visualizer_openai_api_key', sanitize_text_field( $_POST['visualizer_openai_api_key'] ) );
217+
$new_key = sanitize_text_field( $_POST['visualizer_openai_api_key'] );
218+
if ( $new_key !== $this->_maskAPIKey( $current_openai ) ) {
219+
update_option( 'visualizer_openai_api_key', $new_key );
220+
}
183221
}
184222

185-
// Only update Gemini key if a new value is provided
223+
// Only update Gemini key if a new value is provided and it's not the masked version
186224
if ( isset( $_POST['visualizer_gemini_api_key'] ) && ! empty( $_POST['visualizer_gemini_api_key'] ) ) {
187-
update_option( 'visualizer_gemini_api_key', sanitize_text_field( $_POST['visualizer_gemini_api_key'] ) );
225+
$new_key = sanitize_text_field( $_POST['visualizer_gemini_api_key'] );
226+
if ( $new_key !== $this->_maskAPIKey( $current_gemini ) ) {
227+
update_option( 'visualizer_gemini_api_key', $new_key );
228+
}
188229
}
189230

190-
// Only update Claude key if a new value is provided
231+
// Only update Claude key if a new value is provided and it's not the masked version
191232
if ( isset( $_POST['visualizer_claude_api_key'] ) && ! empty( $_POST['visualizer_claude_api_key'] ) ) {
192-
update_option( 'visualizer_claude_api_key', sanitize_text_field( $_POST['visualizer_claude_api_key'] ) );
233+
$new_key = sanitize_text_field( $_POST['visualizer_claude_api_key'] );
234+
if ( $new_key !== $this->_maskAPIKey( $current_claude ) ) {
235+
update_option( 'visualizer_claude_api_key', $new_key );
236+
}
193237
}
194238
}
195239

classes/Visualizer/Render/Page/Types.php

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,22 +175,37 @@ protected function _renderContent() {
175175
echo '<script type="text/javascript">';
176176
echo '(function() {';
177177
echo ' var scrollLocked = true;';
178+
echo ' var scrollAttempts = 0;';
178179
echo ' function forceScrollToTop() {';
179180
echo ' if (scrollLocked) {';
181+
echo ' scrollAttempts++;';
180182
echo ' window.scrollTo(0, 0);';
183+
echo ' document.documentElement.scrollTop = 0;';
184+
echo ' document.body.scrollTop = 0;';
181185
echo ' if (window.parent !== window && window.parent.scrollTo) {';
182-
echo ' try { window.parent.scrollTo(0, 0); } catch(e) {}';
186+
echo ' try { ';
187+
echo ' window.parent.scrollTo(0, 0); ';
188+
echo ' window.parent.document.documentElement.scrollTop = 0;';
189+
echo ' window.parent.document.body.scrollTop = 0;';
190+
echo ' } catch(e) {}';
183191
echo ' }';
184192
echo ' }';
185193
echo ' }';
194+
echo ' document.addEventListener("scroll", forceScrollToTop, true);';
186195
echo ' window.addEventListener("scroll", forceScrollToTop, true);';
187-
echo ' window.addEventListener("DOMContentLoaded", forceScrollToTop);';
196+
echo ' document.addEventListener("DOMContentLoaded", forceScrollToTop);';
188197
echo ' window.addEventListener("load", forceScrollToTop);';
189-
echo ' var intervals = [0, 50, 100, 200, 300, 500, 800, 1000, 1500, 2000];';
198+
echo ' if (document.readyState === "loading") {';
199+
echo ' document.addEventListener("readystatechange", forceScrollToTop);';
200+
echo ' }';
201+
echo ' var intervals = [0, 10, 50, 100, 150, 200, 250, 300, 400, 500, 600, 800, 1000, 1200, 1500, 2000, 2500];';
190202
echo ' intervals.forEach(function(delay) {';
191203
echo ' setTimeout(forceScrollToTop, delay);';
192204
echo ' });';
193-
echo ' setTimeout(function() { scrollLocked = false; }, 2500);';
205+
echo ' setTimeout(function() { ';
206+
echo ' scrollLocked = false; ';
207+
echo ' console.log("Visualizer: Scroll lock released after " + scrollAttempts + " forced resets");';
208+
echo ' }, 3000);';
194209
echo '})();';
195210
echo '</script>';
196211
}
@@ -235,8 +250,8 @@ private function render_chart_selection() {
235250

236251
$select = '';
237252
if ( ! empty( $libraries ) ) {
238-
$select .= '<label for="chart-library">' . __( 'Select Library for charts', 'visualizer' ) . '</label>';
239-
$select .= '<select name="chart-library" class="viz-select-library" data-type-vs-library="' . esc_attr( json_encode( $type_vs_library ) ) . '">';
253+
$select .= '<label>' . __( 'Select Library for charts', 'visualizer' ) . '</label>';
254+
$select .= '<select name="chart-library" class="viz-select-library" data-type-vs-library="' . esc_attr( json_encode( $type_vs_library ) ) . '" tabindex="-1">';
240255
foreach ( $libraries as $library ) {
241256
$select .= '<option value="' . $this->_removeSpaceFromLibrary( $library ) . '">' . $library . '</option>';
242257
}

0 commit comments

Comments
 (0)